@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,215 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Public-facing detail view for `/onboarding-guides/<slug>` on
5
+ * openframe.
6
+ *
7
+ * Self-contained: every concern that used to require a hub-side
8
+ * wrapper now flows through lib primitives + the ChatRuntime context.
9
+ *
10
+ * - Markdown: lib `<SimpleMarkdownRenderer>` (already lib-resident).
11
+ * - Video warmup: lib `useVideoWarmup` (reads Supabase storage
12
+ * origin from `runtime.endpoints.supabaseStorageOrigin`; Mux
13
+ * origins are hardcoded public CDN hosts).
14
+ * - Captions URL: lib `getCaptionsUrl` (pure URL builder).
15
+ * - Video poster: inline priority chain
16
+ * (`main_video_thumbnail || featured_image || og_image_url`) —
17
+ * same fallbacks the card uses.
18
+ * - Related card hrefs: `runtime.composeContentUrl?.(
19
+ * 'onboarding_guide', slug, platforms)` — falls back to same-
20
+ * origin relative path when no composer is wired.
21
+ *
22
+ * No hub-side wrapper file required. Optional `MarkdownRenderer`
23
+ * prop lets hosts swap in a renderer with extra plugins (Reddit /
24
+ * Twitter / X embeds) when needed.
25
+ */
26
+
27
+ import { type ComponentType, type ReactNode } from 'react'
28
+ import { ArrowLeft } from 'lucide-react'
29
+
30
+ import { Link } from '../../embed-shims'
31
+ import { ArticleDetailLayout } from '../layout/article-detail-layout'
32
+ import { EntityVideoSection } from '../features/entity-video-section'
33
+ import { VideoBitesDisplay } from '../features/video-bites-display'
34
+ import { useVideoWarmup } from '../features/use-video-warmup'
35
+ import { getCaptionsUrl } from '../features/captions-url'
36
+ import { SimpleMarkdownRenderer } from '../ui/simple-markdown-renderer'
37
+ import { EntityAuthorCard } from '../chat/entity-cards/entity-author-card'
38
+ import { OnboardingGuideCard } from '../chat/entity-cards/onboarding-guide-card'
39
+ import { useChatRuntime } from '../../contexts/chat-runtime-context'
40
+ import type { OnboardingGuide } from '../chat/types/entities/onboarding-guide'
41
+ import type { VideoTeaser } from '../../types/video-processing'
42
+ import { buildDefaultHref } from './build-default-href'
43
+
44
+ export interface OnboardingGuideDetailViewProps {
45
+ initialData: OnboardingGuide
46
+ related?: OnboardingGuide[]
47
+ /** Optional markdown renderer override. Defaults to lib
48
+ * `<SimpleMarkdownRenderer>`. Hosts override when they need extra
49
+ * plugins (Reddit / Twitter / X / code-block enhancements). */
50
+ MarkdownRenderer?: ComponentType<{ content: string }>
51
+ /** Optional per-row related-card renderer override. When omitted,
52
+ * lib renders `<OnboardingGuideCard>` with runtime-composed href. */
53
+ renderRelatedCard?: (guide: OnboardingGuide) => ReactNode
54
+ /** Back-link target. Defaults to `basePath` so the link returns to
55
+ * the catalog the embedder is hosting (no drift when `basePath`
56
+ * is overridden). The admin preview explicitly overrides to
57
+ * `/admin/onboarding-guides`. */
58
+ backHref?: string
59
+ /** Back-link label. Defaults to "Back to Getting Started". */
60
+ backLabel?: string
61
+ /** Base path the related-card hrefs default to when
62
+ * `runtime.composeContentUrl` is not wired. Embedders mounting at
63
+ * `/docs/onboarding/` instead of `/onboarding-guides/` should
64
+ * override. Default `/onboarding-guides`. */
65
+ basePath?: string
66
+ }
67
+
68
+ export function OnboardingGuideDetailView({
69
+ initialData: guide,
70
+ related = [],
71
+ MarkdownRenderer = SimpleMarkdownRenderer,
72
+ renderRelatedCard,
73
+ backHref,
74
+ backLabel = 'Back to Getting Started',
75
+ basePath = '/onboarding-guides',
76
+ }: OnboardingGuideDetailViewProps) {
77
+ // Resolve `backHref` from `basePath` when not explicitly set — so
78
+ // an embedder overriding `basePath="/docs/onboarding"` automatically
79
+ // gets the right back link without remembering to thread `backHref`
80
+ // too. Admin preview still wins via its explicit `backHref` override.
81
+ const resolvedBackHref = backHref ?? basePath
82
+ const runtime = useChatRuntime()
83
+
84
+ // Video warmup — preconnect always fires; preload only when the
85
+ // container scrolls within ~1 viewport AND the URL is on the
86
+ // configured Supabase storage origin. No origin in runtime ⇒
87
+ // preconnect-only path (Mux/YouTube unaffected).
88
+ const { ref: videoWarmupRef } = useVideoWarmup<HTMLDivElement>({
89
+ videoUrl: guide.main_video_url,
90
+ supabaseStorageOrigin: runtime?.endpoints.supabaseStorageOrigin,
91
+ })
92
+
93
+ const captionsUrl = getCaptionsUrl(
94
+ 'onboarding_guide',
95
+ guide.id,
96
+ guide.srt_content,
97
+ )
98
+
99
+ // Video poster — fallback chain:
100
+ // 1. Entity-owned thumbnails (main_video_thumbnail / featured_image / og_image_url)
101
+ // 2. Branded OG placeholder from `runtime.resolvePlaceholderUrl(title)`
102
+ // — restores the hub's prior behavior where a video without a
103
+ // thumbnail still showed a branded poster instead of a black
104
+ // frame.
105
+ // 3. `undefined` (player picks its own default poster — usually
106
+ // first-frame extraction).
107
+ const videoPoster =
108
+ guide.main_video_thumbnail ||
109
+ guide.featured_image ||
110
+ guide.og_image_url ||
111
+ runtime?.resolvePlaceholderUrl?.(guide.title, { aspect: 'wide' }) ||
112
+ undefined
113
+
114
+ // Default related-card renderer — runtime-composed cross-platform href.
115
+ const defaultRenderRelatedCard = (g: OnboardingGuide) => {
116
+ const cta = runtime?.composeContentUrl
117
+ ? runtime.composeContentUrl(
118
+ 'onboarding_guide',
119
+ g.slug,
120
+ g.onboarding_guide_platforms,
121
+ )
122
+ : buildDefaultHref(basePath, g.slug)
123
+ return (
124
+ <OnboardingGuideCard
125
+ guide={g}
126
+ href={cta.href}
127
+ targetPlatform={cta.targetPlatform}
128
+ />
129
+ )
130
+ }
131
+ const renderRelatedCardFn = renderRelatedCard ?? defaultRenderRelatedCard
132
+
133
+ return (
134
+ <ArticleDetailLayout>
135
+ <div className="space-y-6 md:space-y-8">
136
+ {/* Back link */}
137
+ <Link
138
+ href={resolvedBackHref}
139
+ className="inline-flex items-center gap-2 text-ods-text-secondary hover:text-ods-accent transition-colors"
140
+ >
141
+ <ArrowLeft className="h-4 w-4" />
142
+ <span className="text-h5">{backLabel}</span>
143
+ </Link>
144
+
145
+ <h1 className="text-h1 tracking-[-1.12px] text-ods-text-primary">
146
+ {guide.title}
147
+ </h1>
148
+
149
+ {/* Metadata grid — Section · Step | Published | Author. */}
150
+ <EntityAuthorCard
151
+ author={guide.author}
152
+ publishedAt={guide.published_at}
153
+ extraCells={[
154
+ {
155
+ value: `${guide.section} · Step ${guide.step_order}`,
156
+ label: 'Section',
157
+ uppercase: false,
158
+ },
159
+ ]}
160
+ />
161
+
162
+ {/* Video. `main_video_url` (Mux/MP4) and `youtube_url` are
163
+ independent columns — either one populated should render
164
+ the player. `EntityVideoSection` routes accordingly. */}
165
+ {(guide.main_video_url || guide.youtube_url) && (
166
+ <div ref={videoWarmupRef}>
167
+ <EntityVideoSection
168
+ mainVideoUrl={guide.main_video_url}
169
+ youtubeUrl={guide.youtube_url || undefined}
170
+ highlightVideoUrl={guide.highlight_video_url}
171
+ mainVideoPoster={videoPoster}
172
+ highlightVideoThumbnail={guide.highlight_video_thumbnail || undefined}
173
+ videoSummary={undefined}
174
+ videoBites={undefined}
175
+ title={guide.title}
176
+ srtContent={guide.srt_content}
177
+ captionsUrl={captionsUrl}
178
+ MarkdownRenderer={MarkdownRenderer}
179
+ />
180
+ </div>
181
+ )}
182
+
183
+ {/* Markdown body */}
184
+ {guide.content && (
185
+ <div className="space-y-4">
186
+ <MarkdownRenderer content={guide.content} />
187
+ </div>
188
+ )}
189
+
190
+ {/* Video Bites */}
191
+ {guide.video_bites && guide.video_bites.length > 0 && (
192
+ <VideoBitesDisplay
193
+ bites={guide.video_bites as VideoTeaser[]}
194
+ filterPublished={true}
195
+ showTitle={false}
196
+ />
197
+ )}
198
+
199
+ {/* Related — same-section, ordered by step. */}
200
+ {related.length > 0 && (
201
+ <div className="space-y-4 pt-8 border-t border-ods-border">
202
+ <h2 className="text-h3 tracking-[-0.36px] text-ods-text-primary">
203
+ More in {guide.section}
204
+ </h2>
205
+ <ul className="flex flex-col gap-3">
206
+ {related.map((r) => (
207
+ <li key={r.id}>{renderRelatedCardFn(r)}</li>
208
+ ))}
209
+ </ul>
210
+ </div>
211
+ )}
212
+ </div>
213
+ </ArticleDetailLayout>
214
+ )
215
+ }
@@ -0,0 +1,62 @@
1
+ 'use client'
2
+
3
+ import { DevSectionPage } from '../shared/dev-section'
4
+ import { OnboardingGuideCardSkeleton } from '../chat/entity-cards/onboarding-guide-card'
5
+
6
+ /**
7
+ * Page-level Suspense fallback for `/onboarding-guides`.
8
+ *
9
+ * Mirrors the loaded `<OnboardingGuidesCatalogView>` shape — both
10
+ * mount the same `<DevSectionPage sectionKey="onboarding">` so the
11
+ * hero, back button, and overall page scaffold render identically.
12
+ *
13
+ * The `preControls` slot reserves space for the search bar (h-12)
14
+ * plus the section pill row (~74px including padding) so the
15
+ * Suspense → loaded transition doesn't shift vertically.
16
+ *
17
+ * Card distribution `4 + 3 + 3 = 10` matches the typical openframe
18
+ * onboarding dataset; per-card height (288 px) is byte-identical to
19
+ * the loaded card so per-card shifts on resolve are zero.
20
+ */
21
+ export function OnboardingGuidesCatalogSkeleton() {
22
+ return (
23
+ <DevSectionPage
24
+ sectionKey="onboarding"
25
+ preControls={
26
+ <div className="space-y-4 animate-pulse">
27
+ {/* Search input placeholder — matches `<SearchInput>` h-12. */}
28
+ <div className="h-12 w-full bg-ods-card border border-ods-border rounded-md" />
29
+ {/* Section pill row placeholder — same wrapper class set the
30
+ hub-side `<FilterSection>` uses (~74 px). */}
31
+ <div className="flex flex-wrap items-center gap-3 p-4 bg-ods-card border border-ods-border rounded-lg">
32
+ <div className="h-4 w-14 bg-ods-border/60 rounded" />
33
+ {[0, 1, 2, 3].map((i) => (
34
+ <div
35
+ key={i}
36
+ className="h-10 w-24 bg-ods-card border border-ods-border rounded-md"
37
+ />
38
+ ))}
39
+ </div>
40
+ </div>
41
+ }
42
+ >
43
+ <div className="space-y-10 animate-pulse">
44
+ {[4, 3, 3].map((cardCount, sectionIdx) => (
45
+ <section key={sectionIdx} className="space-y-4">
46
+ <h2 className="text-h3 tracking-[-0.36px] text-ods-text-primary flex items-center gap-2">
47
+ <span className="h-6 w-40 bg-ods-border/70 rounded" />
48
+ <span className="h-5 w-8 bg-ods-text-secondary/20 rounded-full" />
49
+ </h2>
50
+ <ul className="flex flex-col gap-4">
51
+ {Array.from({ length: cardCount }).map((_, cardIdx) => (
52
+ <li key={cardIdx}>
53
+ <OnboardingGuideCardSkeleton size="catalog" />
54
+ </li>
55
+ ))}
56
+ </ul>
57
+ </section>
58
+ ))}
59
+ </div>
60
+ </DevSectionPage>
61
+ )
62
+ }
@@ -0,0 +1,230 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Public-facing catalog view for `/onboarding-guides` on openframe.
5
+ *
6
+ * Self-contained: every concern that used to require a hub-side
7
+ * wrapper now flows through lib primitives + the ChatRuntime context.
8
+ *
9
+ * - Chrome: `<DevSectionPage sectionKey="onboarding">` (same lib
10
+ * primitive every other dev-center surface uses).
11
+ * - Search bar: lib `<DocSearchBar>` + `useDocSearch` directly,
12
+ * pre-scoped to `tableIds: ['onboarding-guides']`. The chat
13
+ * runtime's `source` discriminates the RAG namespace.
14
+ * - Section filter: lib `<FilterSection>` + URL push via embed-shim
15
+ * `useRouter`/`useSearchParams`.
16
+ * - Cards: lib `<OnboardingGuideCard>` with hrefs composed via
17
+ * `runtime.composeContentUrl?.('onboarding_guide', slug, platforms)`.
18
+ * Falls back to a same-origin relative path when no composer is
19
+ * wired (single-platform embedders).
20
+ *
21
+ * No hub-side wrapper file required.
22
+ */
23
+
24
+ import { useMemo, useTransition, type ReactNode } from 'react'
25
+ import { GraduationCap } from 'lucide-react'
26
+
27
+ import { useRouter, useSearchParams } from '../../embed-shims'
28
+ import { DevSectionPage } from '../shared/dev-section'
29
+ import { DocSearchBar, useDocSearch } from '../shared/doc-search'
30
+ import { FilterPillRow } from '../ui/filter-pill-row'
31
+ import { OnboardingGuideCard } from '../chat/entity-cards/onboarding-guide-card'
32
+ import { useChatRuntime } from '../../contexts/chat-runtime-context'
33
+ import type { OnboardingGuide } from '../chat/types/entities/onboarding-guide'
34
+ import { buildDefaultHref } from './build-default-href'
35
+
36
+ export interface OnboardingGuidesCatalogViewProps {
37
+ initialGuides: OnboardingGuide[]
38
+ initialSections: Array<{
39
+ section: string
40
+ section_order: number
41
+ count: number
42
+ }>
43
+ initialSection?: string
44
+ /** Optional per-row card renderer override. When omitted, lib
45
+ * renders `<OnboardingGuideCard>` with runtime-composed href.
46
+ * Embedders only override to swap the card shape entirely. */
47
+ renderCard?: (guide: OnboardingGuide) => ReactNode
48
+ /** Base path the catalog is mounted under. Used as the fallback
49
+ * `href` prefix for card hrefs when `runtime.composeContentUrl` is
50
+ * not wired. Embedders mounting at `/docs/onboarding/` instead of
51
+ * `/onboarding-guides/` should override. Also used by `setSection`
52
+ * for the `?section=` URL push. Default `/onboarding-guides`. */
53
+ basePath?: string
54
+ }
55
+
56
+ export function OnboardingGuidesCatalogView({
57
+ initialGuides,
58
+ initialSections,
59
+ initialSection = '',
60
+ renderCard,
61
+ basePath = '/onboarding-guides',
62
+ }: OnboardingGuidesCatalogViewProps) {
63
+ const router = useRouter()
64
+ const searchParams = useSearchParams()
65
+ const [isPending, startTransition] = useTransition()
66
+ const runtime = useChatRuntime()
67
+ const activeSection = initialSection || 'all'
68
+
69
+ // Section grouping. Data arrives already filtered server-side via
70
+ // `?section=`; this just buckets the visible rows for the section-
71
+ // header layout — no client-side `.filter()`.
72
+ const grouped = useMemo(() => {
73
+ const map = new Map<
74
+ string,
75
+ { section_order: number; guides: OnboardingGuide[] }
76
+ >()
77
+ for (const g of initialGuides) {
78
+ const existing = map.get(g.section)
79
+ if (existing) {
80
+ if (g.section_order < existing.section_order)
81
+ existing.section_order = g.section_order
82
+ existing.guides.push(g)
83
+ } else {
84
+ map.set(g.section, { section_order: g.section_order, guides: [g] })
85
+ }
86
+ }
87
+ for (const entry of map.values()) {
88
+ entry.guides.sort(
89
+ (a, b) =>
90
+ a.step_order - b.step_order || a.title.localeCompare(b.title),
91
+ )
92
+ }
93
+ return Array.from(map.entries())
94
+ .map(([section, info]) => ({ section, ...info }))
95
+ .sort(
96
+ (a, b) =>
97
+ a.section_order - b.section_order ||
98
+ a.section.localeCompare(b.section),
99
+ )
100
+ }, [initialGuides])
101
+
102
+ // Section-filter options for the lib `<FilterSection>` row.
103
+ const sectionFilterOptions = useMemo(
104
+ () => [
105
+ { value: 'all', label: `All (${initialGuides.length})` },
106
+ ...initialSections.map((s) => ({
107
+ value: s.section,
108
+ label: `${s.section} (${s.count})`,
109
+ })),
110
+ ],
111
+ [initialGuides.length, initialSections],
112
+ )
113
+
114
+ // Section pill change → push `?section=X` so the host RSC re-
115
+ // fetches against the DAL. Wrapped in `useTransition` so the
116
+ // results grid dims while the new payload is in flight.
117
+ const setSection = (value: string) => {
118
+ const params = new URLSearchParams(searchParams.toString())
119
+ if (value === 'all') {
120
+ params.delete('section')
121
+ } else {
122
+ params.set('section', value)
123
+ }
124
+ const qs = params.toString()
125
+ startTransition(() => {
126
+ router.push(qs ? `${basePath}?${qs}` : basePath)
127
+ })
128
+ }
129
+
130
+ // Search bar — scoped to onboarding-guides only via the RAG-search
131
+ // `tableIds` parameter. The hook calls `/api/docs/search` directly;
132
+ // hub or embedder must expose that endpoint (reverse-proxy on
133
+ // non-Next.js hosts).
134
+ const source = runtime?.source ?? 'openframe'
135
+ const docSearch = useDocSearch({
136
+ source,
137
+ baseRoute: basePath,
138
+ onNavigate: (path) => router.push(path),
139
+ tableIds: ['onboarding-guides'],
140
+ })
141
+
142
+ // Per-row card renderer — uses runtime-composed href for cross-
143
+ // platform navigation. Falls back to a same-origin relative URL
144
+ // when no composer is wired.
145
+ const defaultRenderCard = (guide: OnboardingGuide) => {
146
+ const cta = runtime?.composeContentUrl
147
+ ? runtime.composeContentUrl(
148
+ 'onboarding_guide',
149
+ guide.slug,
150
+ guide.onboarding_guide_platforms,
151
+ )
152
+ : buildDefaultHref(basePath, guide.slug)
153
+ return (
154
+ <OnboardingGuideCard
155
+ guide={guide}
156
+ href={cta.href}
157
+ targetPlatform={cta.targetPlatform}
158
+ size="catalog"
159
+ />
160
+ )
161
+ }
162
+ const renderCardFn = renderCard ?? defaultRenderCard
163
+
164
+ const preControls = (
165
+ <div className="space-y-4">
166
+ <DocSearchBar
167
+ placeholder="Search onboarding guides, releases, case studies…"
168
+ query={docSearch.query}
169
+ onQueryChange={docSearch.setQuery}
170
+ results={docSearch.results}
171
+ isLoading={docSearch.isLoading}
172
+ onResultSelect={docSearch.handleResultSelect}
173
+ showDropdown={docSearch.keepDropdownOpen}
174
+ />
175
+ {initialSections.length > 0 && (
176
+ <FilterPillRow
177
+ label="Section"
178
+ selectedValue={activeSection}
179
+ onValueChange={setSection}
180
+ options={sectionFilterOptions}
181
+ />
182
+ )}
183
+ </div>
184
+ )
185
+
186
+ return (
187
+ <DevSectionPage sectionKey="onboarding" preControls={preControls}>
188
+ {initialGuides.length === 0 ? (
189
+ <div className="text-center py-16">
190
+ <GraduationCap className="h-12 w-12 text-ods-text-secondary mx-auto mb-4" />
191
+ <h2 className="text-ods-text-primary font-['DM_Sans'] text-[20px] font-semibold mb-2">
192
+ No onboarding guides found
193
+ </h2>
194
+ <p className="text-ods-text-secondary font-['DM_Sans'] text-[14px]">
195
+ {activeSection !== 'all'
196
+ ? 'No guides in this section yet.'
197
+ : "We're working on the onboarding library. Check back soon."}
198
+ </p>
199
+ </div>
200
+ ) : (
201
+ <div
202
+ className={
203
+ isPending
204
+ ? 'opacity-60 transition-opacity space-y-10'
205
+ : 'space-y-10'
206
+ }
207
+ >
208
+ {grouped.map((sec) => (
209
+ <section key={sec.section} className="space-y-4">
210
+ <h2 className="text-h3 tracking-[-0.36px] text-ods-text-primary flex items-center gap-2">
211
+ {sec.section}
212
+ <span className="inline-flex items-center justify-center rounded-full bg-ods-text-secondary/20 text-ods-text-secondary text-xs font-medium px-2 py-0.5">
213
+ {sec.guides.length}
214
+ </span>
215
+ </h2>
216
+ {/* HORIZONTAL catalog list — single column so consecutive
217
+ steps read top-to-bottom (Step 1 above Step 2 above
218
+ Step 3); a grid would visually reorder them. */}
219
+ <ul className="flex flex-col gap-4">
220
+ {sec.guides.map((guide) => (
221
+ <li key={guide.id}>{renderCardFn(guide)}</li>
222
+ ))}
223
+ </ul>
224
+ </section>
225
+ ))}
226
+ </div>
227
+ )}
228
+ </DevSectionPage>
229
+ )
230
+ }
@@ -0,0 +1,100 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * `<DocSearchBar>` — the canonical RAG-search dropdown surface.
5
+ *
6
+ * Mounted by every doc-search consumer (data-room sidebar, onboarding-
7
+ * guide catalog, and any future surface that needs typeahead against
8
+ * `/api/docs/search`). Wraps `<SearchInput>` with the lib's standard
9
+ * `<DocSearchResultRow>` so the dropdown looks identical everywhere.
10
+ *
11
+ * ## Why a presentation component, not a "search bar that owns its
12
+ * own hook"
13
+ *
14
+ * The data-fetching hook (`useDocSearch`) lives hub-side because it
15
+ * depends on hub-only context (`useDocNavigation`, the rag-table-
16
+ * config registry, the hub's `decideNewTab` helper). Moving the hook
17
+ * would cascade ~5 more file migrations into the lib.
18
+ *
19
+ * Instead, the hook stays hub-side and callers pass its result into
20
+ * this component as plain props. Both consumers shrink to ~5 lines.
21
+ */
22
+
23
+ import type { ReactNode } from 'react'
24
+ import { SearchInput, type SearchResult } from '../../ui/search-input'
25
+ import { DocSearchResultRow } from './doc-search-result-row'
26
+
27
+ export interface DocSearchBarProps {
28
+ placeholder: string
29
+ query: string
30
+ onQueryChange: (value: string) => void
31
+ /** Hook-fetched results. Reuses the lib's `<SearchInput>` `SearchResult`
32
+ * shape directly so callers don't translate. */
33
+ results: SearchResult[]
34
+ isLoading: boolean
35
+ /** Result selection handler. Mirrors `<SearchInput>` — the second
36
+ * `modifiers` argument is preserved so cmd-click / shift-click on
37
+ * a result row still forces new-tab behavior. Hub `useDocSearch`
38
+ * reads these to short-circuit to `window.open()`. */
39
+ onResultSelect: (
40
+ result: SearchResult,
41
+ modifiers?: {
42
+ metaKey?: boolean
43
+ ctrlKey?: boolean
44
+ shiftKey?: boolean
45
+ altKey?: boolean
46
+ button?: number
47
+ },
48
+ ) => void
49
+ /** Lets the caller's hook force the dropdown open after a recent
50
+ * internal action (e.g. result navigation). `undefined` falls back
51
+ * to `<SearchInput>`'s built-in focus/hover heuristics. */
52
+ showDropdown?: boolean
53
+ /** Defaults to 2 — matches the existing data-room and onboarding-
54
+ * guide consumers. Override only if a surface needs different
55
+ * typeahead semantics. */
56
+ minQueryLength?: number
57
+ /** Defaults to 0 — both existing consumers debounce inside the
58
+ * hook, not the input. */
59
+ debounceMs?: number
60
+ className?: string
61
+ /** Optional row-renderer override. Defaults to the lib's standard
62
+ * `<DocSearchResultRow>` (source icon + title + path breadcrumb).
63
+ * Override only when a surface needs custom row chrome. */
64
+ renderResult?: (result: SearchResult, isHighlighted: boolean) => ReactNode
65
+ }
66
+
67
+ export function DocSearchBar({
68
+ placeholder,
69
+ query,
70
+ onQueryChange,
71
+ results,
72
+ isLoading,
73
+ onResultSelect,
74
+ showDropdown,
75
+ minQueryLength = 2,
76
+ debounceMs = 0,
77
+ className = 'w-full',
78
+ renderResult,
79
+ }: DocSearchBarProps) {
80
+ return (
81
+ <SearchInput
82
+ placeholder={placeholder}
83
+ value={query}
84
+ onChange={onQueryChange}
85
+ results={results}
86
+ isLoading={isLoading}
87
+ onResultSelect={onResultSelect}
88
+ showDropdown={showDropdown || undefined}
89
+ debounceMs={debounceMs}
90
+ minQueryLength={minQueryLength}
91
+ className={className}
92
+ renderResult={
93
+ renderResult ??
94
+ ((result, isHighlighted) => (
95
+ <DocSearchResultRow result={result} isHighlighted={isHighlighted} />
96
+ ))
97
+ }
98
+ />
99
+ )
100
+ }
@@ -0,0 +1,73 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * Single row in the `<SearchInput>` dropdown — the standard layout
5
+ * used by every doc-search-backed surface (company-hub data-room
6
+ * search bar, onboarding-guide catalog search, …). Single source of
7
+ * truth for the row appearance so search dropdowns are visually
8
+ * identical everywhere.
9
+ *
10
+ * Resolves the source icon via the same `resolveSourceIcon()`
11
+ * registry the inline chat-card refs use, so a row pointing at e.g.
12
+ * an onboarding-guide surfaces the SAME `<GraduationCap>` glyph the
13
+ * chat card surfaces — no cross-surface drift.
14
+ */
15
+
16
+ import { resolveSourceIcon } from '../../chat/utils/source-row-cta'
17
+ import { formatRelativePath } from './format-relative-path'
18
+
19
+ /**
20
+ * Minimal result shape this row renders. Compatible with any
21
+ * doc-search hook whose result type exposes `{ title?, path?,
22
+ * metadata? }`. The two hub consumers (onboarding-guide catalog,
23
+ * data-room sidebar) both satisfy this shape via their
24
+ * `useDocSearch` hook result.
25
+ */
26
+ export interface DocSearchResultRowEntry {
27
+ title?: string
28
+ path?: string
29
+ metadata?: Record<string, unknown>
30
+ }
31
+
32
+ export interface DocSearchResultRowProps {
33
+ result: DocSearchResultRowEntry
34
+ isHighlighted: boolean
35
+ }
36
+
37
+ export function DocSearchResultRow({
38
+ result,
39
+ isHighlighted,
40
+ }: DocSearchResultRowProps) {
41
+ const docType = (result.metadata?.documentType as string) || undefined
42
+ const sourceRepo = (result.metadata?.sourceRepo as string) || undefined
43
+ const { Icon: SourceIcon, label: iconLabel } = resolveSourceIcon({
44
+ sourceRepo,
45
+ documentType: docType,
46
+ })
47
+ const isGroup = result.metadata?.isGroup as boolean | undefined
48
+
49
+ return (
50
+ <div className="flex items-center gap-3 w-full min-w-0">
51
+ <span
52
+ className="flex-shrink-0 text-ods-text-secondary"
53
+ title={iconLabel}
54
+ >
55
+ <SourceIcon className="size-4" />
56
+ </span>
57
+ <div className="min-w-0 flex-1">
58
+ <div
59
+ className={`text-sm font-medium leading-5 truncate ${
60
+ isHighlighted ? 'text-ods-accent' : 'text-ods-text-primary'
61
+ }`}
62
+ >
63
+ {result.title || result.path}
64
+ </div>
65
+ {!isGroup && result.path?.includes('/') && (
66
+ <div className="text-xs leading-4 text-ods-text-secondary truncate mt-0.5">
67
+ {formatRelativePath(result.path)}
68
+ </div>
69
+ )}
70
+ </div>
71
+ </div>
72
+ )
73
+ }