@flamingo-stack/openframe-frontend-core 0.0.291 → 0.0.292-snapshot.20260619040151

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 (201) hide show
  1. package/dist/{chunk-ODR6A6FC.js → chunk-26PKDALD.js} +7 -67
  2. package/dist/chunk-26PKDALD.js.map +1 -0
  3. package/dist/{chunk-EGSAB76W.js → chunk-2U2M2TG2.js} +4 -4
  4. package/dist/{chunk-EI4WALN2.cjs → chunk-4W7NYJ3B.cjs} +39 -29
  5. package/dist/chunk-4W7NYJ3B.cjs.map +1 -0
  6. package/dist/{chunk-KBKZYJRI.cjs → chunk-5E2HOSSH.cjs} +66 -19
  7. package/dist/chunk-5E2HOSSH.cjs.map +1 -0
  8. package/dist/{chunk-6BWZFRUG.cjs → chunk-6G2INVGG.cjs} +24 -22
  9. package/dist/chunk-6G2INVGG.cjs.map +1 -0
  10. package/dist/{chunk-7NM7DEUK.js → chunk-6GCI7JOE.js} +2 -2
  11. package/dist/{chunk-N6ZM5PYZ.js → chunk-7RIYT7ZH.js} +49 -2
  12. package/dist/chunk-7RIYT7ZH.js.map +1 -0
  13. package/dist/{chunk-AGRSALEN.js → chunk-BRNHX6C6.js} +15 -13
  14. package/dist/chunk-BRNHX6C6.js.map +1 -0
  15. package/dist/{chunk-Z2EOXZBW.js → chunk-DOMJSNXW.js} +262 -162
  16. package/dist/chunk-DOMJSNXW.js.map +1 -0
  17. package/dist/{chunk-7EYWERFT.js → chunk-E4XABBSU.js} +16 -8
  18. package/dist/{chunk-7EYWERFT.js.map → chunk-E4XABBSU.js.map} +1 -1
  19. package/dist/{chunk-ZLN6SM2U.js → chunk-EJXHZX2E.js} +3 -3
  20. package/dist/{chunk-3SDBXXDP.cjs → chunk-EYEW6PTA.cjs} +44 -36
  21. package/dist/chunk-EYEW6PTA.cjs.map +1 -0
  22. package/dist/{chunk-D4R2MUPA.js → chunk-FJDPUPXC.js} +5 -5
  23. package/dist/{chunk-VRSXJ5QJ.js → chunk-FQJK446R.js} +10 -3
  24. package/dist/chunk-FQJK446R.js.map +1 -0
  25. package/dist/{chunk-I6ZPGKZ2.cjs → chunk-FT4FCV7L.cjs} +6 -6
  26. package/dist/{chunk-I6ZPGKZ2.cjs.map → chunk-FT4FCV7L.cjs.map} +1 -1
  27. package/dist/{chunk-Y4JNA4W6.cjs → chunk-J54Z3OCR.cjs} +10 -3
  28. package/dist/chunk-J54Z3OCR.cjs.map +1 -0
  29. package/dist/{chunk-ZYBUFVQV.cjs → chunk-JSOMFVEV.cjs} +30 -30
  30. package/dist/{chunk-ZYBUFVQV.cjs.map → chunk-JSOMFVEV.cjs.map} +1 -1
  31. package/dist/{chunk-D6RK5YXX.cjs → chunk-KXCRGTRN.cjs} +10 -70
  32. package/dist/chunk-KXCRGTRN.cjs.map +1 -0
  33. package/dist/{chunk-5KD3S25X.cjs → chunk-LFGGF7OT.cjs} +139 -2
  34. package/dist/chunk-LFGGF7OT.cjs.map +1 -0
  35. package/dist/{chunk-R2KT5GDD.js → chunk-NSPOYUBH.js} +14 -4
  36. package/dist/chunk-NSPOYUBH.js.map +1 -0
  37. package/dist/{chunk-TYZEMPPH.js → chunk-OQ6X7ZOC.js} +138 -1
  38. package/dist/chunk-OQ6X7ZOC.js.map +1 -0
  39. package/dist/{chunk-2YSC3API.cjs → chunk-RJL6PIOK.cjs} +1091 -991
  40. package/dist/chunk-RJL6PIOK.cjs.map +1 -0
  41. package/dist/{chunk-CSL3ECOF.js → chunk-SOJCR63T.js} +4 -4
  42. package/dist/{chunk-BVNYQDZE.cjs → chunk-TYMUKFP2.cjs} +40 -40
  43. package/dist/{chunk-BVNYQDZE.cjs.map → chunk-TYMUKFP2.cjs.map} +1 -1
  44. package/dist/{chunk-VMRPM24G.cjs → chunk-VTY7S2QG.cjs} +25 -19
  45. package/dist/chunk-VTY7S2QG.cjs.map +1 -0
  46. package/dist/{chunk-Z4GAFSNM.cjs → chunk-X3TSMCKX.cjs} +12 -12
  47. package/dist/{chunk-Z4GAFSNM.cjs.map → chunk-X3TSMCKX.cjs.map} +1 -1
  48. package/dist/{chunk-RSIFEDZX.js → chunk-YICTMMXP.js} +13 -7
  49. package/dist/{chunk-RSIFEDZX.js.map → chunk-YICTMMXP.js.map} +1 -1
  50. package/dist/{chunk-EL5GCMPU.cjs → chunk-YIGPRLQY.cjs} +9 -9
  51. package/dist/{chunk-EL5GCMPU.cjs.map → chunk-YIGPRLQY.cjs.map} +1 -1
  52. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  53. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  54. package/dist/components/chat/embeddable-chat.d.ts +15 -0
  55. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  56. package/dist/components/chat/entity-cards/roadmap-card.d.ts +7 -1
  57. package/dist/components/chat/entity-cards/roadmap-card.d.ts.map +1 -1
  58. package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -1
  59. package/dist/components/chat/index.cjs +9 -7
  60. package/dist/components/chat/index.cjs.map +1 -1
  61. package/dist/components/chat/index.d.ts +1 -0
  62. package/dist/components/chat/index.d.ts.map +1 -1
  63. package/dist/components/chat/index.js +8 -6
  64. package/dist/components/chat/remark-mention-chips.d.ts +30 -0
  65. package/dist/components/chat/remark-mention-chips.d.ts.map +1 -0
  66. package/dist/components/chat/types/api.types.d.ts +4 -0
  67. package/dist/components/chat/types/api.types.d.ts.map +1 -1
  68. package/dist/components/chat/types/component.types.d.ts +24 -0
  69. package/dist/components/chat/types/component.types.d.ts.map +1 -1
  70. package/dist/components/chat/types/context-item.types.d.ts +5 -0
  71. package/dist/components/chat/types/context-item.types.d.ts.map +1 -1
  72. package/dist/components/chat/types/processing.types.d.ts +4 -0
  73. package/dist/components/chat/types/processing.types.d.ts.map +1 -1
  74. package/dist/components/chat/utils/chunk-parser.d.ts.map +1 -1
  75. package/dist/components/chat/utils/nav-anchor-props.d.ts +8 -3
  76. package/dist/components/chat/utils/nav-anchor-props.d.ts.map +1 -1
  77. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  78. package/dist/components/contact/index.cjs +8 -8
  79. package/dist/components/contact/index.js +7 -7
  80. package/dist/components/docs/index.cjs +6 -6
  81. package/dist/components/docs/index.js +5 -5
  82. package/dist/components/docs/use-document-tree.d.ts.map +1 -1
  83. package/dist/components/embeds/index.cjs +8 -8
  84. package/dist/components/embeds/index.js +7 -7
  85. package/dist/components/faq/faq-section.d.ts.map +1 -1
  86. package/dist/components/faq/index.cjs +8 -8
  87. package/dist/components/faq/index.js +7 -7
  88. package/dist/components/features/index.cjs +7 -7
  89. package/dist/components/features/index.js +6 -6
  90. package/dist/components/index.cjs +216 -193
  91. package/dist/components/index.cjs.map +1 -1
  92. package/dist/components/index.js +52 -29
  93. package/dist/components/index.js.map +1 -1
  94. package/dist/components/navigation/index.cjs +7 -7
  95. package/dist/components/navigation/index.js +6 -6
  96. package/dist/components/onboarding-guides/index.cjs +24 -24
  97. package/dist/components/onboarding-guides/index.js +4 -4
  98. package/dist/components/related-content/index.cjs +8 -8
  99. package/dist/components/related-content/index.js +7 -7
  100. package/dist/components/shared/delivery/delivery-lists.d.ts.map +1 -1
  101. package/dist/components/shared/delivery/delivery-row.d.ts +8 -1
  102. package/dist/components/shared/delivery/delivery-row.d.ts.map +1 -1
  103. package/dist/components/shared/delivery/delivery-table.d.ts.map +1 -1
  104. package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -1
  105. package/dist/components/shared/roadmap/roadmap-view.d.ts.map +1 -1
  106. package/dist/components/tickets/help-center-card.d.ts +7 -1
  107. package/dist/components/tickets/help-center-card.d.ts.map +1 -1
  108. package/dist/components/tickets/help-center-list.d.ts.map +1 -1
  109. package/dist/components/tickets/index.cjs +82 -73
  110. package/dist/components/tickets/index.cjs.map +1 -1
  111. package/dist/components/tickets/index.js +24 -15
  112. package/dist/components/tickets/index.js.map +1 -1
  113. package/dist/components/tickets/ticket-center.d.ts.map +1 -1
  114. package/dist/components/tickets/ticket-row.d.ts +6 -1
  115. package/dist/components/tickets/ticket-row.d.ts.map +1 -1
  116. package/dist/components/ui/index.cjs +9 -7
  117. package/dist/components/ui/index.cjs.map +1 -1
  118. package/dist/components/ui/index.js +8 -6
  119. package/dist/components/ui/simple-markdown-renderer.d.ts.map +1 -1
  120. package/dist/components/ui/tag.d.ts +10 -1
  121. package/dist/components/ui/tag.d.ts.map +1 -1
  122. package/dist/hooks/index.cjs +5 -3
  123. package/dist/hooks/index.cjs.map +1 -1
  124. package/dist/hooks/index.d.ts +1 -0
  125. package/dist/hooks/index.d.ts.map +1 -1
  126. package/dist/hooks/index.js +4 -2
  127. package/dist/hooks/use-scroll-to-hash.d.ts +17 -0
  128. package/dist/hooks/use-scroll-to-hash.d.ts.map +1 -0
  129. package/dist/index.cjs +21 -7
  130. package/dist/index.cjs.map +1 -1
  131. package/dist/index.js +21 -7
  132. package/dist/utils/dev-sections/dev-section-param-keys.d.ts +10 -0
  133. package/dist/utils/dev-sections/dev-section-param-keys.d.ts.map +1 -1
  134. package/dist/utils/index.cjs +92 -10
  135. package/dist/utils/index.cjs.map +1 -1
  136. package/dist/utils/index.d.ts +2 -1
  137. package/dist/utils/index.d.ts.map +1 -1
  138. package/dist/utils/index.js +88 -11
  139. package/dist/utils/index.js.map +1 -1
  140. package/dist/utils/same-page-hash-nav.d.ts +37 -0
  141. package/dist/utils/same-page-hash-nav.d.ts.map +1 -0
  142. package/dist/utils/scroll-into-view.d.ts +12 -0
  143. package/dist/utils/scroll-into-view.d.ts.map +1 -1
  144. package/dist/utils/source-icons.d.ts.map +1 -1
  145. package/package.json +1 -1
  146. package/src/components/chat/chat-message-enhanced.tsx +71 -9
  147. package/src/components/chat/chat-message-list.tsx +2 -0
  148. package/src/components/chat/embeddable-chat.tsx +50 -6
  149. package/src/components/chat/entity-cards/roadmap-card.tsx +8 -1
  150. package/src/components/chat/hooks/use-realtime-chunk-processor.ts +1 -0
  151. package/src/components/chat/index.ts +1 -0
  152. package/src/components/chat/remark-mention-chips.ts +72 -0
  153. package/src/components/chat/types/api.types.ts +1 -1
  154. package/src/components/chat/types/component.types.ts +18 -0
  155. package/src/components/chat/types/context-item.types.ts +5 -0
  156. package/src/components/chat/types/processing.types.ts +8 -1
  157. package/src/components/chat/utils/chunk-parser.ts +11 -0
  158. package/src/components/chat/utils/nav-anchor-props.ts +22 -4
  159. package/src/components/chat/utils/process-historical-messages.ts +22 -0
  160. package/src/components/docs/use-document-tree.ts +45 -3
  161. package/src/components/faq/faq-section.tsx +22 -9
  162. package/src/components/shared/delivery/delivery-lists.tsx +9 -0
  163. package/src/components/shared/delivery/delivery-row.tsx +15 -2
  164. package/src/components/shared/delivery/delivery-table.tsx +7 -1
  165. package/src/components/shared/roadmap/roadmap-grid.tsx +7 -0
  166. package/src/components/shared/roadmap/roadmap-view.tsx +11 -0
  167. package/src/components/tickets/help-center-card.tsx +9 -17
  168. package/src/components/tickets/help-center-list.tsx +13 -0
  169. package/src/components/tickets/ticket-center.tsx +2 -0
  170. package/src/components/tickets/ticket-row.tsx +7 -1
  171. package/src/components/ui/simple-markdown-renderer.tsx +14 -11
  172. package/src/components/ui/tag.tsx +12 -2
  173. package/src/hooks/index.ts +5 -0
  174. package/src/hooks/use-scroll-to-hash.ts +74 -0
  175. package/src/utils/.source-icons.md +1 -1
  176. package/src/utils/dev-sections/dev-section-param-keys.ts +14 -0
  177. package/src/utils/index.ts +17 -1
  178. package/src/utils/same-page-hash-nav.ts +115 -0
  179. package/src/utils/scroll-into-view.ts +51 -9
  180. package/src/utils/source-icons.ts +7 -1
  181. package/dist/chunk-2YSC3API.cjs.map +0 -1
  182. package/dist/chunk-3SDBXXDP.cjs.map +0 -1
  183. package/dist/chunk-5KD3S25X.cjs.map +0 -1
  184. package/dist/chunk-6BWZFRUG.cjs.map +0 -1
  185. package/dist/chunk-AGRSALEN.js.map +0 -1
  186. package/dist/chunk-D6RK5YXX.cjs.map +0 -1
  187. package/dist/chunk-EI4WALN2.cjs.map +0 -1
  188. package/dist/chunk-KBKZYJRI.cjs.map +0 -1
  189. package/dist/chunk-N6ZM5PYZ.js.map +0 -1
  190. package/dist/chunk-ODR6A6FC.js.map +0 -1
  191. package/dist/chunk-R2KT5GDD.js.map +0 -1
  192. package/dist/chunk-TYZEMPPH.js.map +0 -1
  193. package/dist/chunk-VMRPM24G.cjs.map +0 -1
  194. package/dist/chunk-VRSXJ5QJ.js.map +0 -1
  195. package/dist/chunk-Y4JNA4W6.cjs.map +0 -1
  196. package/dist/chunk-Z2EOXZBW.js.map +0 -1
  197. /package/dist/{chunk-EGSAB76W.js.map → chunk-2U2M2TG2.js.map} +0 -0
  198. /package/dist/{chunk-7NM7DEUK.js.map → chunk-6GCI7JOE.js.map} +0 -0
  199. /package/dist/{chunk-ZLN6SM2U.js.map → chunk-EJXHZX2E.js.map} +0 -0
  200. /package/dist/{chunk-D4R2MUPA.js.map → chunk-FJDPUPXC.js.map} +0 -0
  201. /package/dist/{chunk-CSL3ECOF.js.map → chunk-SOJCR63T.js.map} +0 -0
@@ -27,7 +27,7 @@ const iconName = getSourceIconName('github-commits')
27
27
 
28
28
  // Get display label for a chip strip
29
29
  const label = getSourceLabel('hubspot-tickets-anon')
30
- // → 'Known Issues'
30
+ // → 'Tickets'
31
31
 
32
32
  // Map a documentType emitted by the LLM to its table ID
33
33
  const tableId = defaultTableIdForDocumentType('slack_message')
@@ -19,3 +19,17 @@ export const DEV_SECTION_PARAM_KEYS = {
19
19
  /** Delivery (bug-fix / enhancement) task-type filter. */
20
20
  deliveryTaskType: 'task_type',
21
21
  } as const
22
+
23
+ /** Section keys that participate in `<section>-<id>` anchor IDs.
24
+ * The URL composer (`appendSearchAndHash` in hub `dev-section-url.ts`)
25
+ * and the row components (`DeliveryRow`, `RoadmapCard`, `HelpCenterCard`)
26
+ * both call `devSectionAnchorId` so the DOM `id` and the URL hash stay
27
+ * in lockstep — adding a new section means adding the literal here
28
+ * ONCE, not at every render site. */
29
+ export type DevSectionAnchorKind = 'roadmap' | 'delivery' | 'ticket'
30
+
31
+ /** Compose the canonical `<section>-<id>` anchor id used by the dev-center
32
+ * rows + URL composer. */
33
+ export function devSectionAnchorId(section: DevSectionAnchorKind, id: string): string {
34
+ return `${section}-${id}`
35
+ }
@@ -44,7 +44,11 @@ export * from './release-cover'
44
44
  // Dev-center URL param keys — the ONE source for the `?search=` / `?status=` / … keys the
45
45
  // chrome registry writes and the list views read; re-exported so embedders (and the hub's
46
46
  // dev-section-url helper) build deep-links with the same keys instead of a bare literal.
47
- export { DEV_SECTION_PARAM_KEYS } from './dev-sections/dev-section-param-keys'
47
+ export {
48
+ DEV_SECTION_PARAM_KEYS,
49
+ devSectionAnchorId,
50
+ type DevSectionAnchorKind,
51
+ } from './dev-sections/dev-section-param-keys'
48
52
  // Dynamic icon registry — single source of truth lives at
49
53
  // components/chat/utils/icon-registry. Re-exported here so existing
50
54
  // `@flamingo-stack/openframe-frontend-core/utils` callers (hub admin
@@ -246,6 +250,18 @@ export {
246
250
  scrollElementIntoView,
247
251
  } from './scroll-into-view'
248
252
 
253
+ // Same-page hash navigation — owns pushState + synthetic hashchange +
254
+ // anchoring-proof scroll. Pair of `scrollElementIntoView`. Used by the
255
+ // hub's `useUnifiedNav` + chat-runtime `navigate`, AND by every
256
+ // embeddable surface that drives state off the URL hash.
257
+ export {
258
+ navigateSamePageHash,
259
+ normalizeHashFragment,
260
+ STICKY_HEADER_OFFSET_PX,
261
+ HUB_HEADER_OFFSET_PX,
262
+ type NavigateSamePageHashOptions,
263
+ } from './same-page-hash-nav'
264
+
249
265
  // Shared list-API URL builder — the single source for the per-type chat
250
266
  // entity-card fetch shapes. The hub's 12 RAG mapper `listApi` closures
251
267
  // delegate here (byte-parity test guards the migration); embedders wire
@@ -0,0 +1,115 @@
1
+ import { scrollElementIntoView } from './scroll-into-view'
2
+
3
+ /** Pages with a section-nav STRIP on top of the global hub header
4
+ * (dev-center roadmap/delivery/tickets, FAQ category-pill nav).
5
+ * Anchor lands BELOW both layers. */
6
+ export const STICKY_HEADER_OFFSET_PX = 96
7
+
8
+ /** Pages with only the global hub header (docs, blog, vendor detail).
9
+ * Anchor lands BELOW the header bar. */
10
+ export const HUB_HEADER_OFFSET_PX = 80
11
+
12
+ /**
13
+ * Take only the FIRST hash segment from a fragment that may contain extra
14
+ * `#` characters. `'' → ''`, `'#a' → '#a'`, `'#a#b' → '#a'`.
15
+ *
16
+ * No real DOM id contains `#`, so a multi-fragment hash is always a bug at
17
+ * the composer site; `navigateSamePageHash` + `useScrollToHash` both call
18
+ * this so URL bar and `getElementById` stay in sync.
19
+ */
20
+ export function normalizeHashFragment(hash: string): string {
21
+ if (!hash) return ''
22
+ const second = hash.indexOf('#', 1)
23
+ return second < 0 ? hash : hash.slice(0, second)
24
+ }
25
+
26
+ export interface NavigateSamePageHashOptions {
27
+ /** Pixels to subtract for sticky chrome. */
28
+ headerOffset?: number
29
+ /** `'push'` (default) — new history entry; `'replace'` — overwrite
30
+ * current entry (use for TOC-style in-page navigators). */
31
+ history?: 'push' | 'replace'
32
+ }
33
+
34
+ /**
35
+ * Same-page hash navigation primitive: pushState + synthetic `hashchange`
36
+ * + anchoring-proof smooth scroll. Replaces `router.push` for hash CTAs
37
+ * (Next.js suppresses smooth-scroll during navigation; `router.push` on
38
+ * an exact-URL match is a no-op). Returns `true` when the helper claimed
39
+ * the nav (same pathname + search); `false` for cross-page targets so
40
+ * callers fall through to `router.push`.
41
+ *
42
+ * `target` accepts an origin-stripped path (`/x#anchor`) or a bare hash
43
+ * (`#anchor`); bare-hash callers don't need to reconstruct `pathname +
44
+ * search` themselves.
45
+ */
46
+ export function navigateSamePageHash(
47
+ target: string,
48
+ options: NavigateSamePageHashOptions = {},
49
+ ): boolean {
50
+ if (typeof window === 'undefined') return false
51
+ const { headerOffset = 0, history: historyMode = 'push' } = options
52
+ const normalizedTarget =
53
+ target.startsWith('#')
54
+ ? window.location.pathname + window.location.search + target
55
+ : target
56
+ // `new URL(absoluteUrl, base)` ignores `base` per RFC 3986; an absolute
57
+ // cross-origin target sharing pathname/search would otherwise pass the
58
+ // check below and trip pushState's same-origin enforcement. Parse with
59
+ // an explicit base so malformed inputs cleanly fall through.
60
+ let url: URL
61
+ try {
62
+ url = new URL(normalizedTarget, window.location.href)
63
+ } catch {
64
+ return false
65
+ }
66
+ if (
67
+ url.origin !== window.location.origin ||
68
+ url.pathname !== window.location.pathname ||
69
+ url.search !== window.location.search
70
+ ) {
71
+ return false
72
+ }
73
+ const current = window.location.pathname + window.location.search + window.location.hash
74
+ // Heal a malformed multi-fragment hash so the URL bar is clean and
75
+ // `getElementById` resolves. Dev-warn fingers the upstream composer.
76
+ const normalizedHash = normalizeHashFragment(url.hash)
77
+ if (process.env.NODE_ENV === 'development' && normalizedHash !== url.hash) {
78
+ // eslint-disable-next-line no-console
79
+ console.warn(
80
+ `[navigateSamePageHash] malformed fragment "${url.hash}" → normalizing to "${normalizedHash}". Fix the upstream composer.`,
81
+ )
82
+ }
83
+ const next = url.pathname + url.search + normalizedHash
84
+ const id = normalizedHash && normalizedHash !== '#' ? normalizedHash.slice(1) : ''
85
+ // Hash-less targets are only ours on an EXACT URL re-click.
86
+ if (!id && next !== current) return false
87
+ if (next !== current) {
88
+ const oldURL = window.location.href
89
+ if (historyMode === 'replace') {
90
+ window.history.replaceState(null, '', next)
91
+ } else {
92
+ window.history.pushState(null, '', next)
93
+ }
94
+ // Synthetic `hashchange` — `pushState` doesn't fire it (HTML spec),
95
+ // so URL-hash-bound listeners (FAQ auto-expand, etc.) wouldn't react.
96
+ window.dispatchEvent(new HashChangeEvent('hashchange', {
97
+ oldURL,
98
+ newURL: window.location.href,
99
+ }))
100
+ }
101
+ const el = id ? document.getElementById(id) : null
102
+ if (id && !el && process.env.NODE_ENV === 'development') {
103
+ // eslint-disable-next-line no-console
104
+ console.warn(
105
+ `[navigateSamePageHash] anchor "#${id}" not found — scrolling to top.`,
106
+ )
107
+ }
108
+ // Missing anchor → tween to page top. `documentElement` is at 0 by
109
+ // definition, so one tween covers both branches.
110
+ scrollElementIntoView(el ?? document.documentElement, {
111
+ behavior: 'smooth',
112
+ headerOffset,
113
+ })
114
+ return true
115
+ }
@@ -33,6 +33,18 @@
33
33
  *
34
34
  * Honors `prefers-reduced-motion` (jumps instantly) and cancels on genuine user
35
35
  * scroll intent (wheel / touch) so we never fight the user.
36
+ *
37
+ * WINDOW *OR* A SCROLLABLE ANCESTOR: the helper is not hard-wired to the window
38
+ * scroller. It walks up from the target to the nearest ancestor that is an
39
+ * actual scroll container (`overflow-y: auto | scroll | overlay` AND
40
+ * `scrollHeight > clientHeight`) and drives THAT element; only when none exists
41
+ * does it fall back to `window`. This is what makes it work inside app shells
42
+ * that put page content in a fixed-height `<main class="overflow-y-auto">`
43
+ * (e.g. OpenFrame's `AppLayout`) where the document/window never scrolls — the
44
+ * old window-only version was a silent no-op there. Note `overflow: clip` /
45
+ * `hidden` are deliberately NOT treated as scroll containers, so a list wrapper
46
+ * that uses `overflow-clip` only to round its corners still bubbles the scroll
47
+ * up to the real container (matches the `<HelpCenterCard>` list intent).
36
48
  */
37
49
 
38
50
  export interface ScrollElementIntoViewOptions {
@@ -70,6 +82,23 @@ function cancelActiveScroll(): void {
70
82
 
71
83
  const easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3)
72
84
 
85
+ /** Nearest ancestor that is a *real* scroll container, or `null` when the
86
+ * window/document is the scroller. Only `auto | scroll | overlay` count —
87
+ * `clip` / `hidden` are intentionally excluded (a wrapper using `overflow-clip`
88
+ * purely to round corners must let the scroll bubble to the page). */
89
+ function getScrollableAncestor(el: HTMLElement): HTMLElement | null {
90
+ for (let node = el.parentElement; node; node = node.parentElement) {
91
+ const overflowY = getComputedStyle(node).overflowY
92
+ if (
93
+ (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay') &&
94
+ node.scrollHeight > node.clientHeight
95
+ ) {
96
+ return node
97
+ }
98
+ }
99
+ return null
100
+ }
101
+
73
102
  /**
74
103
  * Scroll the page so `target` lands at the top of the viewport (below sticky
75
104
  * chrome via `headerOffset`). SSR-safe; `null`/`undefined` target is a no-op so
@@ -82,16 +111,29 @@ export function scrollElementIntoView(
82
111
  if (typeof window === 'undefined' || !target) return
83
112
  const { headerOffset = 0, behavior = 'smooth', adjustTargetY, durationMs = 320 } = options
84
113
 
114
+ // Pick the scroller ONCE: a fixed-height `<main overflow-y-auto>` shell scrolls
115
+ // the element, a plain document scrolls the window. The choice can't change
116
+ // mid-tween, so resolve it up front and route every read/write through it.
117
+ const container = getScrollableAncestor(target)
118
+ const readCurrent = (): number => (container ? container.scrollTop : window.scrollY)
119
+ const writeTo = (y: number): void => {
120
+ if (container) container.scrollTop = y
121
+ else window.scrollTo(0, y)
122
+ }
123
+
85
124
  // Target is recomputed every frame: the row's absolute position can move as
86
125
  // the page reflows (a sibling drawer collapsing) and the reachable max grows
87
126
  // as the just-opened drawer expands. Clamp to the LIVE max each frame.
88
127
  const computeTarget = (): number => {
89
- const raw = target.getBoundingClientRect().top + window.scrollY - headerOffset
128
+ const raw = container
129
+ ? container.scrollTop +
130
+ (target.getBoundingClientRect().top - container.getBoundingClientRect().top) -
131
+ headerOffset
132
+ : target.getBoundingClientRect().top + window.scrollY - headerOffset
90
133
  const adjusted = adjustTargetY ? adjustTargetY(raw) : raw
91
- const maxScroll = Math.max(
92
- 0,
93
- document.documentElement.scrollHeight - window.innerHeight,
94
- )
134
+ const maxScroll = container
135
+ ? Math.max(0, container.scrollHeight - container.clientHeight)
136
+ : Math.max(0, document.documentElement.scrollHeight - window.innerHeight)
95
137
  return Math.min(Math.max(0, adjusted), maxScroll)
96
138
  }
97
139
 
@@ -104,7 +146,7 @@ export function scrollElementIntoView(
104
146
 
105
147
  // Instant paths: a single synchronous write. No tween, no anchoring race.
106
148
  if (behavior === 'instant' || behavior === 'auto' || prefersReduced) {
107
- window.scrollTo(0, computeTarget())
149
+ writeTo(computeTarget())
108
150
  return
109
151
  }
110
152
 
@@ -125,18 +167,18 @@ export function scrollElementIntoView(
125
167
 
126
168
  const step = (now: number) => {
127
169
  if (startY === null) {
128
- startY = window.scrollY
170
+ startY = readCurrent()
129
171
  startTime = now
130
172
  }
131
173
  const targetY = computeTarget()
132
174
  const t = Math.min(1, (now - startTime) / durationMs)
133
175
  const y = startY + (targetY - startY) * easeOutCubic(t)
134
- window.scrollTo(0, y)
176
+ writeTo(y)
135
177
  if (t < 1) {
136
178
  activeRaf = requestAnimationFrame(step)
137
179
  } else {
138
180
  // Final exact write in case easing left a sub-pixel gap, then teardown.
139
- window.scrollTo(0, computeTarget())
181
+ writeTo(computeTarget())
140
182
  activeRaf = 0
141
183
  if (teardownActive) {
142
184
  teardownActive()
@@ -128,7 +128,13 @@ export const SOURCE_LABELS_BY_TABLE: Record<string, string> = {
128
128
 
129
129
  // HubSpot
130
130
  'hubspot-tickets': 'HubSpot Tickets',
131
- 'hubspot-tickets-anon': 'Known Issues',
131
+ // Anon + self share the "Tickets" root so the chip vocabulary is
132
+ // uniform across logged-out (everyone's resolved support tickets,
133
+ // anonymized → "Tickets") and logged-in (user-scoped → "My Tickets")
134
+ // surfaces. The full-PII `hubspot-tickets` entry above is product-hub
135
+ // internal only (admin view), kept as "HubSpot Tickets" to flag the
136
+ // distinct scope.
137
+ 'hubspot-tickets-anon': 'Tickets',
132
138
  'hubspot-tickets-self': 'My Tickets',
133
139
 
134
140
  // Communications