@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.215",
3
+ "version": "0.0.216",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -48,6 +48,12 @@
48
48
  "require": "./dist/components/tickets/index.cjs",
49
49
  "default": "./dist/components/tickets/index.js"
50
50
  },
51
+ "./components/onboarding-guides": {
52
+ "types": "./dist/components/onboarding-guides/index.d.ts",
53
+ "import": "./dist/components/onboarding-guides/index.js",
54
+ "require": "./dist/components/onboarding-guides/index.cjs",
55
+ "default": "./dist/components/onboarding-guides/index.js"
56
+ },
51
57
  "./components/contact": {
52
58
  "types": "./dist/components/contact/index.d.ts",
53
59
  "import": "./dist/components/contact/index.js",
@@ -72,6 +78,12 @@
72
78
  "require": "./dist/components/features/index.cjs",
73
79
  "default": "./dist/components/features/index.js"
74
80
  },
81
+ "./components/features/mux-origins": {
82
+ "types": "./dist/components/features/mux-origins.d.ts",
83
+ "import": "./dist/components/features/mux-origins.js",
84
+ "require": "./dist/components/features/mux-origins.cjs",
85
+ "default": "./dist/components/features/mux-origins.js"
86
+ },
75
87
  "./components/toast": {
76
88
  "types": "./dist/components/toast/index.d.ts",
77
89
  "import": "./dist/components/toast/index.js",
@@ -24,6 +24,8 @@ import Image from '../../../embed-shims/next-image'
24
24
  import { StatusBadge } from '../../ui/status-badge'
25
25
  import { cn } from '../../../utils/cn'
26
26
  import type { BlogPostSummary } from '../../../types/blog'
27
+ import { useEntityCardLink } from './use-entity-card-link'
28
+ import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
27
29
  import {
28
30
  COMPACT_CARD_IMAGE_SLOT,
29
31
  COMPACT_CARD_META_ROW_BOX,
@@ -102,16 +104,26 @@ export function BlogCardSkeleton({ size = 'default' }: { size?: 'default' | 'sm'
102
104
  export function BlogCard({
103
105
  post,
104
106
  href,
105
-
106
- target,
107
-
108
- rel,
109
- placeholderUrl,
107
+ target: targetProp,
108
+ rel: relProp,
109
+ targetPlatform,
110
+ placeholderUrl: placeholderUrlProp,
110
111
  size = 'default',
111
112
  className,
112
113
  hasEmbeddedVideo = false,
113
114
  priority = false,
114
115
  }: BlogCardProps) {
116
+ const { target, rel } = useEntityCardLink({
117
+ href,
118
+ targetPlatform,
119
+ target: targetProp,
120
+ rel: relProp,
121
+ })
122
+ const placeholderUrl = useEntityCardPlaceholder({
123
+ title: post.title,
124
+ placeholderUrl: placeholderUrlProp,
125
+ aspect: size === 'sm' ? 'square' : 'wide',
126
+ })
115
127
  const [imageError, setImageError] = useState(false)
116
128
  const displayImage = (post.featured_image && !imageError) ? post.featured_image : placeholderUrl
117
129
 
@@ -17,6 +17,8 @@ import Image from '../../../embed-shims/next-image'
17
17
  import { Card } from '../../ui/card'
18
18
  import { cn } from '../../../utils/cn'
19
19
  import type { CaseStudy } from '../../../types/case-study'
20
+ import { useEntityCardLink } from './use-entity-card-link'
21
+ import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
20
22
  import {
21
23
  COMPACT_CARD_IMAGE_SLOT,
22
24
  COMPACT_CARD_META_ROW_BOX,
@@ -88,7 +90,27 @@ export function CaseStudyCardSkeleton({ size = 'default' }: { size?: 'default' |
88
90
  )
89
91
  }
90
92
 
91
- export function CaseStudyCard({ study, href, target, rel, placeholderUrl, size = 'default', className }: CaseStudyCardProps) {
93
+ export function CaseStudyCard({
94
+ study,
95
+ href,
96
+ target: targetProp,
97
+ rel: relProp,
98
+ targetPlatform,
99
+ placeholderUrl: placeholderUrlProp,
100
+ size = 'default',
101
+ className,
102
+ }: CaseStudyCardProps) {
103
+ const { target, rel } = useEntityCardLink({
104
+ href,
105
+ targetPlatform,
106
+ target: targetProp,
107
+ rel: relProp,
108
+ })
109
+ const placeholderUrl = useEntityCardPlaceholder({
110
+ title: study.title,
111
+ placeholderUrl: placeholderUrlProp,
112
+ aspect: size === 'sm' ? 'square' : 'wide',
113
+ })
92
114
  const coverImage = study.featured_image || placeholderUrl || null
93
115
 
94
116
  if (size === 'sm') {
@@ -13,6 +13,8 @@ import { Card } from '../../ui/card'
13
13
  import { cn } from '../../../utils/cn'
14
14
  import { Video } from 'lucide-react'
15
15
  import type { CustomerInterview } from '../../../types/customer-interview'
16
+ import { useEntityCardLink } from './use-entity-card-link'
17
+ import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
16
18
  import {
17
19
  COMPACT_CARD_IMAGE_SLOT,
18
20
  COMPACT_CARD_META_ROW_BOX,
@@ -88,7 +90,27 @@ export function CustomerInterviewCardSkeleton({ size = 'default' }: { size?: 'de
88
90
  )
89
91
  }
90
92
 
91
- export function CustomerInterviewCard({ interview, href, target, rel, placeholderUrl, size = 'default', className }: CustomerInterviewCardProps) {
93
+ export function CustomerInterviewCard({
94
+ interview,
95
+ href,
96
+ target: targetProp,
97
+ rel: relProp,
98
+ targetPlatform,
99
+ placeholderUrl: placeholderUrlProp,
100
+ size = 'default',
101
+ className,
102
+ }: CustomerInterviewCardProps) {
103
+ const { target, rel } = useEntityCardLink({
104
+ href,
105
+ targetPlatform,
106
+ target: targetProp,
107
+ rel: relProp,
108
+ })
109
+ const placeholderUrl = useEntityCardPlaceholder({
110
+ title: interview.title,
111
+ placeholderUrl: placeholderUrlProp,
112
+ aspect: size === 'sm' ? 'square' : 'wide',
113
+ })
92
114
  const thumbnailUrl = interview.featured_image || placeholderUrl || null
93
115
 
94
116
  if (size === 'sm') {
@@ -1016,6 +1016,27 @@ export function ChatCardLoader({
1016
1016
  </ChatCardNavWrap>
1017
1017
  )
1018
1018
  if (entry.mode === 'no-fetch') {
1019
+ // Synthetic-ref gate. `chat-message-enhanced.tsx` builds a minimal
1020
+ // `{ type, id, title: cardId, url: null }` ChatRef when the LLM
1021
+ // emits `[card://<type>:<id>]` for an id the server did NOT
1022
+ // surface (refs map miss) — typically an LLM hallucination of a
1023
+ // composite/invented UUID. EVERY real ref carries `sourceRepo`
1024
+ // (set by `buildChatRefFromRow` via `config.id` AND by
1025
+ // `synthesizeVideoRefs` via `EMBEDDED_VIDEO_SOURCE_REPO`), so a
1026
+ // missing `sourceRepo` is a reliable synthetic-ref signal.
1027
+ //
1028
+ // Returning null here triggers the bare-cardId fallback span in
1029
+ // chat-message-enhanced's `<a card://...>` override — the
1030
+ // documented "VISIBLE breakage" behavior. Without this gate, a
1031
+ // hallucinated marker like `[card://markdown:f18945f8-<real-uuid>]`
1032
+ // renders a `DataRoomDocChatCard` with the generic "Document"
1033
+ // badge AND the fake id as the title — which the user can't tell
1034
+ // apart from a real card (the entire point of the fallback
1035
+ // comment block in chat-message-enhanced.tsx).
1036
+ //
1037
+ // Fetch-mode types already handle this gracefully: a synthetic
1038
+ // id leads to a fetch miss → `!item` → null at line ~1034.
1039
+ if (!finalChatRef.sourceRepo) return null
1019
1040
  if (entry.bareInline) {
1020
1041
  return navWrap(entry.render(finalChatRef, renderOpts))
1021
1042
  }
@@ -12,6 +12,8 @@ import React from 'react'
12
12
  import { Calendar } from 'lucide-react'
13
13
  import { AdminContentCard } from './admin-content-card'
14
14
  import { formatInvestorUpdatePeriod, type InvestorUpdate } from '../types/entities/investor-update'
15
+ import { useEntityCardLink } from './use-entity-card-link'
16
+ import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
15
17
  import {
16
18
  COMPACT_CARD_IMAGE_SLOT,
17
19
  COMPACT_CARD_META_ROW_BOX,
@@ -73,7 +75,27 @@ export function InvestorUpdateCardSkeleton({ size = 'default' }: { size?: 'defau
73
75
  )
74
76
  }
75
77
 
76
- export function InvestorUpdateCard({ update, href, target, rel, placeholderUrl, size = 'default', className }: InvestorUpdateCardProps) {
78
+ export function InvestorUpdateCard({
79
+ update,
80
+ href,
81
+ target: targetProp,
82
+ rel: relProp,
83
+ targetPlatform,
84
+ placeholderUrl: placeholderUrlProp,
85
+ size = 'default',
86
+ className,
87
+ }: InvestorUpdateCardProps) {
88
+ const { target, rel } = useEntityCardLink({
89
+ href,
90
+ targetPlatform,
91
+ target: targetProp,
92
+ rel: relProp,
93
+ })
94
+ const placeholderUrl = useEntityCardPlaceholder({
95
+ title: update.title ?? `Update #${update.update_number ?? ''}`,
96
+ placeholderUrl: placeholderUrlProp,
97
+ aspect: size === 'sm' ? 'square' : 'wide',
98
+ })
77
99
  const coverImage = update.featured_image || placeholderUrl || null
78
100
 
79
101
  if (size === 'sm') {
@@ -1,7 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  /**
4
- * OnboardingGuideCard (pure presentation).
4
+ * OnboardingGuideCard (pure presentation + runtime-derived link attrs).
5
5
  *
6
6
  * Three variants:
7
7
  * - `catalog`: rich detail card (hero + author grid) for the public catalog
@@ -9,8 +9,11 @@
9
9
  * - `default`: horizontal step-numbered card for "More in {section}" rail.
10
10
  * - `sm`: compact horizontal card for chat-inline rendering.
11
11
  *
12
- * The card writes NO click logic callers wrap with their own anchor and
13
- * pass the resolved detail URL via `href`.
12
+ * Link semantics: the card derives `target`/`rel` from `ChatRuntime.navigation
13
+ * .decideNewTab` (hub-wired via `HubRuntimeProvider`) and the placeholder
14
+ * image from `ChatRuntime.resolvePlaceholderUrl`. Explicit `target` / `rel`
15
+ * / `placeholderUrl` props always WIN — chat dispatch and tests can
16
+ * pre-resolve. No runtime mounted → same-tab + no placeholder.
14
17
  */
15
18
 
16
19
  import React from 'react'
@@ -21,6 +24,8 @@ import { BlogImagePlaceholder } from './blog-image-placeholder'
21
24
  import { EntityAuthorCard } from './entity-author-card'
22
25
  import { formatDurationMMSS } from '../../../utils/format'
23
26
  import type { OnboardingGuide } from '../types/entities/onboarding-guide'
27
+ import { useEntityCardLink } from './use-entity-card-link'
28
+ import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
24
29
  import {
25
30
  COMPACT_CARD_OUTER,
26
31
  COMPACT_CARD_IMAGE_SLOT,
@@ -134,7 +139,28 @@ export function OnboardingGuideCardSkeleton({ size = 'default' }: { size?: 'cata
134
139
  )
135
140
  }
136
141
 
137
- export function OnboardingGuideCard({ guide, href, target, rel, placeholderUrl, size = 'default', className }: OnboardingGuideCardProps) {
142
+ export function OnboardingGuideCard({
143
+ guide,
144
+ href,
145
+ target: targetProp,
146
+ rel: relProp,
147
+ targetPlatform,
148
+ placeholderUrl: placeholderUrlProp,
149
+ size = 'default',
150
+ className,
151
+ }: OnboardingGuideCardProps) {
152
+ const { target, rel } = useEntityCardLink({
153
+ href,
154
+ targetPlatform,
155
+ target: targetProp,
156
+ rel: relProp,
157
+ })
158
+ const placeholderUrl = useEntityCardPlaceholder({
159
+ title: guide.title,
160
+ placeholderUrl: placeholderUrlProp,
161
+ aspect: size === 'sm' ? 'square' : 'wide',
162
+ })
163
+
138
164
  if (size === 'catalog') {
139
165
  const coverImage =
140
166
  guide.featured_image ||
@@ -40,6 +40,8 @@ import type {
40
40
  ProgramMedia,
41
41
  ProgramHost,
42
42
  } from '../types/entities/program-types'
43
+ import { useEntityCardLink } from './use-entity-card-link'
44
+ import { useEntityCardPlaceholder } from './use-entity-card-placeholder'
43
45
 
44
46
  type CardSize = 'default' | 'sm'
45
47
 
@@ -198,12 +200,24 @@ export function ProgramCard<T extends BaseProgramItem>({
198
200
  renderMeta,
199
201
  size = 'default',
200
202
  href,
201
- target,
202
- rel,
203
- placeholderUrl,
203
+ target: targetProp,
204
+ rel: relProp,
205
+ targetPlatform,
206
+ placeholderUrl: placeholderUrlProp,
204
207
  wholeCardClickable = false,
205
208
  className,
206
209
  }: ProgramCardProps<T>) {
210
+ const { target, rel } = useEntityCardLink({
211
+ href,
212
+ targetPlatform,
213
+ target: targetProp,
214
+ rel: relProp,
215
+ })
216
+ const placeholderUrl = useEntityCardPlaceholder({
217
+ title: item.title,
218
+ placeholderUrl: placeholderUrlProp,
219
+ aspect: size === 'sm' ? 'square' : 'wide',
220
+ })
207
221
  const coverImage = item.cover_url
208
222
  const images = media.filter((m) => m.media_type === 'image')
209
223
  const hosts = getHosts(item.hosts)
@@ -0,0 +1,66 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Shared link-attribute resolver for every entity card.
5
+ *
6
+ * Pure-presentation cards (BlogCard, OnboardingGuideCard, etc.)
7
+ * historically required callers to pre-compute `target`/`rel` via a
8
+ * hub-side `useNavLink` wrapper because the same-tab-vs-new-tab
9
+ * decision depends on `currentPlatform()` and the cross-platform
10
+ * URL topology — neither of which the lib knows.
11
+ *
12
+ * This hook moves that decision INTO the card via the
13
+ * `ChatRuntime.navigation.decideNewTab` callback already wired by
14
+ * `HubRuntimeProvider` (and overridable per-embedder). The wrapping
15
+ * `*-card-item.tsx` files in the hub become unnecessary — every card
16
+ * derives its own `target`/`rel` from runtime.
17
+ *
18
+ * Backwards compat: explicit `target` / `rel` props always WIN over
19
+ * runtime-derived values. Chat dispatcher callsites that already pass
20
+ * pre-resolved attributes are unaffected.
21
+ *
22
+ * No runtime mounted? Returns `{ target: undefined, rel: undefined }`
23
+ * (same-tab) — matches the documented embed-shim fallback.
24
+ */
25
+
26
+ import { useMemo } from 'react'
27
+ import { useChatRuntime } from '../../../contexts/chat-runtime-context'
28
+
29
+ export interface UseEntityCardLinkArgs {
30
+ href: string
31
+ targetPlatform?: string | null
32
+ /** Explicit override. When set, runtime decision is skipped. */
33
+ target?: '_blank'
34
+ /** Explicit override. When set, runtime decision is skipped. */
35
+ rel?: 'noopener noreferrer'
36
+ }
37
+
38
+ export interface EntityCardLinkProps {
39
+ target: '_blank' | undefined
40
+ rel: 'noopener noreferrer' | undefined
41
+ }
42
+
43
+ export function useEntityCardLink({
44
+ href,
45
+ targetPlatform,
46
+ target,
47
+ rel,
48
+ }: UseEntityCardLinkArgs): EntityCardLinkProps {
49
+ const runtime = useChatRuntime()
50
+ return useMemo(() => {
51
+ // Explicit prop wins — preserves the chat-dispatcher path that
52
+ // pre-computes attrs from `computeIsNewTab`. When `target='_blank'`
53
+ // is passed without `rel`, auto-pair with `noopener noreferrer`
54
+ // to close the tabnabbing vector (window.opener access from the
55
+ // new tab back to the parent).
56
+ if (target !== undefined || rel !== undefined) {
57
+ const safeRel =
58
+ rel ?? (target === '_blank' ? 'noopener noreferrer' : undefined)
59
+ return { target, rel: safeRel }
60
+ }
61
+ const newTab = runtime?.navigation.decideNewTab?.({ href, targetPlatform }) ?? false
62
+ return newTab
63
+ ? { target: '_blank' as const, rel: 'noopener noreferrer' as const }
64
+ : { target: undefined, rel: undefined }
65
+ }, [target, rel, href, targetPlatform, runtime])
66
+ }
@@ -0,0 +1,50 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Shared OG-placeholder resolver for every entity card.
5
+ *
6
+ * Pure-presentation cards historically required callers to
7
+ * pre-compute `placeholderUrl` via a hub-side `useOgPlaceholder`
8
+ * wrapper that injected the hub's `buildOgPlaceholderUrl` (resolves
9
+ * CSS-var ODS colors to hex via the static map).
10
+ *
11
+ * This hook moves that resolution INTO the card via the
12
+ * `ChatRuntime.resolvePlaceholderUrl` callback. Embedders that don't
13
+ * wire the callback get no placeholder (the card's empty-state path
14
+ * activates) — same fallback semantics as before.
15
+ *
16
+ * Backwards compat: explicit `placeholderUrl` prop ALWAYS wins over
17
+ * runtime-derived value. Callers that pre-resolve (chat dispatch,
18
+ * tests) are unaffected.
19
+ */
20
+
21
+ import { useChatRuntime } from '../../../contexts/chat-runtime-context'
22
+ import { useOgPlaceholder } from '../../../hooks/use-og-placeholder'
23
+
24
+ export interface UseEntityCardPlaceholderArgs {
25
+ /** Entity title — used as the placeholder label. */
26
+ title: string | undefined | null
27
+ /** Explicit override. When set, runtime resolver is skipped. */
28
+ placeholderUrl?: string | null
29
+ /** Site name shown under the title. Optional. */
30
+ siteName?: string
31
+ /** Output aspect ratio. `'wide'` (default) for catalog cards,
32
+ * `'square'` for compact chat-inline cards. */
33
+ aspect?: 'wide' | 'square'
34
+ }
35
+
36
+ const NO_OP_BUILDER = () => ''
37
+
38
+ export function useEntityCardPlaceholder({
39
+ title,
40
+ placeholderUrl,
41
+ siteName = '',
42
+ aspect = 'wide',
43
+ }: UseEntityCardPlaceholderArgs): string | null {
44
+ const runtime = useChatRuntime()
45
+ const builder = runtime?.resolvePlaceholderUrl ?? NO_OP_BUILDER
46
+ const enabled = placeholderUrl === undefined && !!runtime?.resolvePlaceholderUrl
47
+ const derived = useOgPlaceholder(builder, title, siteName, enabled, aspect)
48
+ // Explicit prop (including explicit null) wins; `undefined` falls back to derived.
49
+ return placeholderUrl !== undefined ? placeholderUrl : derived
50
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Build the captions API URL for a video entity.
3
+ *
4
+ * Returns the HTTPS URL to the `/api/captions/[entityType]/[entityId]` endpoint
5
+ * which serves VTT content for iOS native fullscreen subtitles.
6
+ * Returns undefined if entity has no srt_content.
7
+ *
8
+ * Cache-busting hash derived from the srt_content length so iOS Safari
9
+ * fetches fresh VTT when subtitles are regenerated (Safari aggressively caches
10
+ * <track> src URLs even with short Cache-Control max-age).
11
+ *
12
+ * Lifted from hub `lib/utils/captions-url.ts`. The hub's hard-coded
13
+ * `VideoEnabledEntityType` enum is widened to `string` here — embedders
14
+ * pass whatever entity-type discriminator their reverse-proxied
15
+ * `/api/captions/...` route expects.
16
+ */
17
+ export function getCaptionsUrl(
18
+ entityType: string,
19
+ entityId: string | number,
20
+ srtContent?: string | null,
21
+ ): string | undefined {
22
+ if (!srtContent) return undefined
23
+ const hash = `${srtContent.length}-${srtContent.slice(0, 8).replace(/\s/g, '')}`
24
+ return `/api/captions/${entityType}/${entityId}?v=${hash}`
25
+ }
@@ -56,6 +56,8 @@ export * from './video'
56
56
  export * from './video-ratio-tabs'
57
57
  export * from './video-bites-display'
58
58
  export * from './entity-video-section'
59
+ export * from './use-video-warmup'
60
+ export * from './captions-url'
59
61
  export * from './video-source-selector'
60
62
  export * from './transcript-summary-editor'
61
63
  export * from './highlight-video-section'
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Mux CDN origin constants — single source of truth, server-safe.
3
+ *
4
+ * Lives in its own NON-`'use client'` module so server-side hub
5
+ * modules (webhook handlers, URL builders, hostname comparisons in
6
+ * `lib/config/mux-config.ts`) can import these strings without
7
+ * tripping Next.js's client-reference poisoning. Re-exported from
8
+ * `use-video-warmup.ts` for backward-compat with client-side callers.
9
+ *
10
+ * Bug history (2026-05-29): when these constants lived in
11
+ * `use-video-warmup.ts` (which is `'use client'`), the hub's
12
+ * server-side `new URL(MUX_STREAM_ORIGIN).hostname` evaluation crashed
13
+ * at Vercel build with `TypeError: Invalid URL` — Next.js had
14
+ * replaced the constant with a client-function stub that throws
15
+ * "Attempted to call ... from the server" when stringified. Splitting
16
+ * the constants into this module restores the server-safe path.
17
+ *
18
+ * These hostnames are part of Mux's public API contract and are
19
+ * stable. A future change to the Mux CDN architecture (extremely
20
+ * unlikely) would be a single-line edit here.
21
+ */
22
+
23
+ /** HLS playback (`/{playback_id}.m3u8` + segments + per-asset MP4 renditions). */
24
+ export const MUX_STREAM_ORIGIN = 'https://stream.mux.com'
25
+
26
+ /** Server-generated thumbnails (`/{playback_id}/thumbnail.jpg`). */
27
+ export const MUX_IMAGE_ORIGIN = 'https://image.mux.com'
@@ -8,6 +8,8 @@ export { NotificationDrawer } from './notification-drawer'
8
8
  export type { NotificationDrawerProps } from './notification-drawer'
9
9
  export { NotificationTile } from './notification-tile'
10
10
  export type { NotificationTileProps } from './notification-tile'
11
+ export { NotificationPopups } from './notification-popups'
12
+ export type { NotificationPopupsProps, NotificationPopupsPosition } from './notification-popups'
11
13
  export type {
12
14
  Notification,
13
15
  NotificationVariant,
@@ -1,6 +1,7 @@
1
1
  'use client'
2
2
 
3
3
  import * as DialogPrimitive from '@radix-ui/react-dialog'
4
+ import { useEffect, useRef } from 'react'
4
5
  import { BellOffIcon } from '../../icons-v2-generated/interface/bell-off-icon'
5
6
  import { ClockHistoryIcon } from '../../icons-v2-generated/date-and-time/clock-history-icon'
6
7
  import { Button } from '../../ui/button/button'
@@ -9,13 +10,15 @@ import { Switch } from '../../ui/switch'
9
10
  import { cn } from '../../../utils/cn'
10
11
  import { useOptionalNotifications } from './notifications-context'
11
12
  import { NotificationTile } from './notification-tile'
13
+ import type { Notification } from './types'
12
14
 
13
15
  export interface NotificationDrawerProps {
14
16
  className?: string
15
17
  liveDurationMs?: number
18
+ loadMoreRootMargin?: string
16
19
  }
17
20
 
18
- export function NotificationDrawer({ className, liveDurationMs }: NotificationDrawerProps) {
21
+ export function NotificationDrawer({ className, liveDurationMs, loadMoreRootMargin = '200px' }: NotificationDrawerProps) {
19
22
  const ctx = useOptionalNotifications()
20
23
  if (!ctx) return null
21
24
 
@@ -31,6 +34,9 @@ export function NotificationDrawer({ className, liveDurationMs }: NotificationDr
31
34
  markSettled,
32
35
  setShowPopups,
33
36
  onHistoryClick,
37
+ hasMore,
38
+ isLoadingMore,
39
+ loadMore,
34
40
  } = ctx
35
41
 
36
42
  const unreadNotifications = notifications.filter((n) => !n.read)
@@ -59,21 +65,16 @@ export function NotificationDrawer({ className, liveDurationMs }: NotificationDr
59
65
  </button>
60
66
  </div>
61
67
 
62
- <div className="flex flex-1 flex-col gap-[var(--spacing-system-xs)] overflow-y-auto px-[var(--spacing-system-m)]">
63
- {unreadNotifications.length === 0 ? (
64
- <EmptyState />
65
- ) : (
66
- unreadNotifications.map((n) => (
67
- <NotificationTile
68
- key={n.id}
69
- notification={n}
70
- liveDurationMs={liveDurationMs}
71
- onComplete={markRead}
72
- onSettle={markSettled}
73
- />
74
- ))
75
- )}
76
- </div>
68
+ <DrawerScrollList
69
+ unreadNotifications={unreadNotifications}
70
+ liveDurationMs={liveDurationMs}
71
+ onComplete={markRead}
72
+ onSettle={markSettled}
73
+ hasMore={hasMore}
74
+ isLoadingMore={isLoadingMore}
75
+ loadMore={loadMore}
76
+ loadMoreRootMargin={loadMoreRootMargin}
77
+ />
77
78
 
78
79
  <div className="flex flex-col gap-[var(--spacing-system-xs)] px-[var(--spacing-system-m)] pb-[var(--spacing-system-m)]">
79
80
  <ShowNotificationsToggleRow checked={showPopups} onChange={setShowPopups} />
@@ -86,6 +87,89 @@ export function NotificationDrawer({ className, liveDurationMs }: NotificationDr
86
87
  )
87
88
  }
88
89
 
90
+ interface DrawerScrollListProps {
91
+ unreadNotifications: Notification[]
92
+ liveDurationMs?: number
93
+ onComplete: (id: string) => void
94
+ onSettle: (id: string) => void
95
+ hasMore: boolean
96
+ isLoadingMore: boolean
97
+ loadMore?: () => void
98
+ loadMoreRootMargin: string
99
+ }
100
+
101
+ function DrawerScrollList({
102
+ unreadNotifications,
103
+ liveDurationMs,
104
+ onComplete,
105
+ onSettle,
106
+ hasMore,
107
+ isLoadingMore,
108
+ loadMore,
109
+ loadMoreRootMargin,
110
+ }: DrawerScrollListProps) {
111
+ const scrollRef = useRef<HTMLDivElement>(null)
112
+ const sentinelRef = useRef<HTMLDivElement>(null)
113
+ const loadMoreRef = useRef(loadMore)
114
+ loadMoreRef.current = loadMore
115
+
116
+ useEffect(() => {
117
+ if (!loadMore || !hasMore || isLoadingMore) return
118
+ const sentinel = sentinelRef.current
119
+ const root = scrollRef.current
120
+ if (!sentinel || !root) return
121
+ const observer = new IntersectionObserver(
122
+ entries => {
123
+ if (entries[0]?.isIntersecting) loadMoreRef.current?.()
124
+ },
125
+ { root, rootMargin: loadMoreRootMargin },
126
+ )
127
+ observer.observe(sentinel)
128
+ return () => observer.disconnect()
129
+ }, [hasMore, isLoadingMore, loadMore, loadMoreRootMargin])
130
+
131
+ const isEmpty = unreadNotifications.length === 0
132
+ return (
133
+ <div
134
+ ref={scrollRef}
135
+ className="flex flex-1 flex-col gap-[var(--spacing-system-xs)] overflow-y-auto px-[var(--spacing-system-m)]"
136
+ >
137
+ {isEmpty && !isLoadingMore ? (
138
+ <EmptyState />
139
+ ) : (
140
+ <>
141
+ {unreadNotifications.map((n) => (
142
+ <NotificationTile
143
+ key={n.id}
144
+ notification={n}
145
+ liveDurationMs={liveDurationMs}
146
+ onComplete={onComplete}
147
+ onSettle={onSettle}
148
+ />
149
+ ))}
150
+ {isLoadingMore && <DrawerLoadingTiles />}
151
+ {loadMore && hasMore && <div ref={sentinelRef} className="h-1" aria-hidden="true" />}
152
+ </>
153
+ )}
154
+ </div>
155
+ )
156
+ }
157
+
158
+ function DrawerLoadingTiles() {
159
+ return (
160
+ <>
161
+ <div
162
+ aria-hidden="true"
163
+ className="h-16 w-full shrink-0 animate-pulse rounded-md border border-ods-border bg-ods-card"
164
+ />
165
+ <div
166
+ aria-hidden="true"
167
+ className="h-16 w-full shrink-0 animate-pulse rounded-md border border-ods-border bg-ods-card"
168
+ />
169
+ </>
170
+ )
171
+ }
172
+
89
173
  function EmptyState() {
90
174
  return (
91
175
  <div className="flex flex-1 flex-col items-center justify-center gap-[var(--spacing-system-xs)] py-[var(--spacing-system-l)]">