@flamingo-stack/openframe-frontend-core 0.0.274 → 0.0.275-snapshot.20260616145353

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 (179) hide show
  1. package/dist/{chunk-XDGDCO3I.cjs → chunk-2GLHJ7ZY.cjs} +45 -44
  2. package/dist/chunk-2GLHJ7ZY.cjs.map +1 -0
  3. package/dist/{chunk-FLIWL6V4.js → chunk-62U6DFIT.js} +4 -4
  4. package/dist/{chunk-5QJQAFTP.cjs → chunk-6IJWWLC3.cjs} +48 -12
  5. package/dist/chunk-6IJWWLC3.cjs.map +1 -0
  6. package/dist/{chunk-4YKGXZ7W.js → chunk-7AH75KFQ.js} +1 -1
  7. package/dist/chunk-7AH75KFQ.js.map +1 -0
  8. package/dist/{chunk-2KHFSSFZ.cjs → chunk-DPMW5E6Q.cjs} +15 -15
  9. package/dist/{chunk-2KHFSSFZ.cjs.map → chunk-DPMW5E6Q.cjs.map} +1 -1
  10. package/dist/{chunk-T7AJBKZP.cjs → chunk-GVSTRN36.cjs} +5 -2
  11. package/dist/chunk-GVSTRN36.cjs.map +1 -0
  12. package/dist/{chunk-4DYXV3E7.cjs → chunk-IAB7QACX.cjs} +1218 -1227
  13. package/dist/chunk-IAB7QACX.cjs.map +1 -0
  14. package/dist/{chunk-L4LAAA5Q.js → chunk-IMNGGLKL.js} +5 -2
  15. package/dist/chunk-IMNGGLKL.js.map +1 -0
  16. package/dist/{chunk-7NJUPRN6.cjs → chunk-LH7R56RR.cjs} +184 -209
  17. package/dist/chunk-LH7R56RR.cjs.map +1 -0
  18. package/dist/{chunk-WU6PMH5L.cjs → chunk-MYJKC56A.cjs} +41 -43
  19. package/dist/chunk-MYJKC56A.cjs.map +1 -0
  20. package/dist/{chunk-GL5X6VVC.js → chunk-NITJSVUM.js} +4 -4
  21. package/dist/{chunk-CYMMGPPV.js → chunk-NPKMH446.js} +41 -5
  22. package/dist/chunk-NPKMH446.js.map +1 -0
  23. package/dist/{chunk-MEAR2WHS.cjs → chunk-PC7YOZ4I.cjs} +1 -1
  24. package/dist/chunk-PC7YOZ4I.cjs.map +1 -0
  25. package/dist/{chunk-QLWBBCI5.cjs → chunk-Q2ITV3NY.cjs} +5 -4
  26. package/dist/chunk-Q2ITV3NY.cjs.map +1 -0
  27. package/dist/{chunk-VASNLWPZ.js → chunk-QNWFTSAG.js} +5 -7
  28. package/dist/{chunk-VASNLWPZ.js.map → chunk-QNWFTSAG.js.map} +1 -1
  29. package/dist/chunk-RGALV4UX.js +433 -0
  30. package/dist/chunk-RGALV4UX.js.map +1 -0
  31. package/dist/{chunk-W5KBDFCR.cjs → chunk-SITWWJWS.cjs} +30 -30
  32. package/dist/{chunk-W5KBDFCR.cjs.map → chunk-SITWWJWS.cjs.map} +1 -1
  33. package/dist/{chunk-N5MCKGAB.js → chunk-UCX75VEM.js} +34 -33
  34. package/dist/{chunk-N5MCKGAB.js.map → chunk-UCX75VEM.js.map} +1 -1
  35. package/dist/{chunk-ZMFT5XOB.js → chunk-UXJCZOLA.js} +4 -3
  36. package/dist/chunk-UXJCZOLA.js.map +1 -0
  37. package/dist/{chunk-OPSZNW3I.js → chunk-VAIPAZPJ.js} +3071 -3080
  38. package/dist/chunk-VAIPAZPJ.js.map +1 -0
  39. package/dist/{chunk-BSOHVQ7X.js → chunk-ZS4XNIZK.js} +367 -392
  40. package/dist/chunk-ZS4XNIZK.js.map +1 -0
  41. package/dist/chunk-ZSUNQJRO.cjs +433 -0
  42. package/dist/chunk-ZSUNQJRO.cjs.map +1 -0
  43. package/dist/components/chat/chat-message-row.d.ts +1 -1
  44. package/dist/components/chat/chat-message-row.d.ts.map +1 -1
  45. package/dist/components/chat/index.cjs +10 -11
  46. package/dist/components/chat/index.cjs.map +1 -1
  47. package/dist/components/chat/index.js +12 -13
  48. package/dist/components/chat/utils/history-merge.d.ts.map +1 -1
  49. package/dist/components/contact/index.cjs +7 -9
  50. package/dist/components/contact/index.cjs.map +1 -1
  51. package/dist/components/contact/index.js +6 -8
  52. package/dist/components/faq/faq-document-page.d.ts +21 -0
  53. package/dist/components/faq/faq-document-page.d.ts.map +1 -0
  54. package/dist/components/faq/index.cjs +22 -5
  55. package/dist/components/faq/index.cjs.map +1 -1
  56. package/dist/components/faq/index.d.ts +1 -0
  57. package/dist/components/faq/index.d.ts.map +1 -1
  58. package/dist/components/faq/index.js +21 -4
  59. package/dist/components/features/board/board.d.ts.map +1 -1
  60. package/dist/components/features/board/ticket-card.d.ts +2 -1
  61. package/dist/components/features/board/ticket-card.d.ts.map +1 -1
  62. package/dist/components/features/board/types.d.ts +1 -0
  63. package/dist/components/features/board/types.d.ts.map +1 -1
  64. package/dist/components/features/index.cjs +6 -8
  65. package/dist/components/features/index.cjs.map +1 -1
  66. package/dist/components/features/index.js +5 -7
  67. package/dist/components/index.cjs +304 -309
  68. package/dist/components/index.cjs.map +1 -1
  69. package/dist/components/index.js +249 -254
  70. package/dist/components/index.js.map +1 -1
  71. package/dist/components/layout/article-detail-layout.d.ts +21 -9
  72. package/dist/components/layout/article-detail-layout.d.ts.map +1 -1
  73. package/dist/components/navigation/index.cjs +6 -8
  74. package/dist/components/navigation/index.cjs.map +1 -1
  75. package/dist/components/navigation/index.js +5 -7
  76. package/dist/components/onboarding-guides/index.cjs +33 -46
  77. package/dist/components/onboarding-guides/index.cjs.map +1 -1
  78. package/dist/components/onboarding-guides/index.js +11 -24
  79. package/dist/components/onboarding-guides/index.js.map +1 -1
  80. package/dist/components/onboarding-guides/onboarding-guide-detail-view.d.ts.map +1 -1
  81. package/dist/components/related-content/index.cjs +8 -10
  82. package/dist/components/related-content/index.cjs.map +1 -1
  83. package/dist/components/related-content/index.js +7 -9
  84. package/dist/components/shared/delivery/delivery-lists.d.ts.map +1 -1
  85. package/dist/components/shared/detail-page-skeleton.d.ts +6 -1
  86. package/dist/components/shared/detail-page-skeleton.d.ts.map +1 -1
  87. package/dist/components/shared/legal-document/use-legal-docs.d.ts.map +1 -1
  88. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  89. package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -1
  90. package/dist/components/shared/roadmap/use-roadmap-voting.d.ts.map +1 -1
  91. package/dist/components/tickets/help-center-list.d.ts +9 -1
  92. package/dist/components/tickets/help-center-list.d.ts.map +1 -1
  93. package/dist/components/tickets/hooks/use-ticket-actions.d.ts.map +1 -1
  94. package/dist/components/tickets/hooks/use-ticket-engagements.d.ts.map +1 -1
  95. package/dist/components/tickets/hooks/use-tickets-list.d.ts.map +1 -1
  96. package/dist/components/tickets/index.cjs +81 -76
  97. package/dist/components/tickets/index.cjs.map +1 -1
  98. package/dist/components/tickets/index.js +27 -22
  99. package/dist/components/tickets/index.js.map +1 -1
  100. package/dist/components/ui/actions-menu.d.ts +6 -0
  101. package/dist/components/ui/actions-menu.d.ts.map +1 -1
  102. package/dist/components/ui/index.cjs +12 -15
  103. package/dist/components/ui/index.cjs.map +1 -1
  104. package/dist/components/ui/index.js +24 -27
  105. package/dist/contexts/chat-runtime-context.d.ts +9 -0
  106. package/dist/contexts/chat-runtime-context.d.ts.map +1 -1
  107. package/dist/contexts/index.cjs +2 -2
  108. package/dist/contexts/index.js +1 -1
  109. package/dist/hooks/index.cjs +3 -3
  110. package/dist/hooks/index.js +2 -2
  111. package/dist/hooks/use-contact-submission.d.ts.map +1 -1
  112. package/dist/hooks/use-self-fetch.d.ts.map +1 -1
  113. package/dist/index.cjs +11 -15
  114. package/dist/index.cjs.map +1 -1
  115. package/dist/index.js +32 -36
  116. package/dist/utils/embed-authed-fetch.d.ts +7 -0
  117. package/dist/utils/embed-authed-fetch.d.ts.map +1 -1
  118. package/dist/utils/embed-content-fetch.d.ts +2 -0
  119. package/dist/utils/embed-content-fetch.d.ts.map +1 -0
  120. package/dist/utils/index.cjs.map +1 -1
  121. package/dist/utils/index.js.map +1 -1
  122. package/package.json +1 -1
  123. package/src/components/chat/chat-message-row.tsx +12 -2
  124. package/src/components/chat/utils/history-merge.ts +27 -0
  125. package/src/components/faq/faq-document-page.tsx +69 -0
  126. package/src/components/faq/index.ts +1 -0
  127. package/src/components/features/board/board-column.tsx +1 -0
  128. package/src/components/features/board/board.tsx +6 -1
  129. package/src/components/features/board/ticket-card.tsx +23 -6
  130. package/src/components/features/board/types.ts +1 -0
  131. package/src/components/layout/article-detail-layout.tsx +24 -11
  132. package/src/components/onboarding-guides/onboarding-guide-detail-view.tsx +21 -16
  133. package/src/components/shared/delivery/delivery-lists.tsx +3 -2
  134. package/src/components/shared/detail-page-skeleton.tsx +18 -10
  135. package/src/components/shared/legal-document/use-legal-docs.ts +2 -1
  136. package/src/components/shared/product-release/release-detail-page.tsx +27 -17
  137. package/src/components/shared/roadmap/roadmap-grid.tsx +2 -1
  138. package/src/components/shared/roadmap/use-roadmap-voting.ts +3 -2
  139. package/src/components/tickets/help-center-list.tsx +12 -4
  140. package/src/components/tickets/hooks/use-ticket-actions.ts +7 -2
  141. package/src/components/tickets/hooks/use-ticket-engagements.ts +6 -1
  142. package/src/components/tickets/hooks/use-tickets-list.ts +7 -1
  143. package/src/components/tickets/ticket-detail-drawer.tsx +1 -0
  144. package/src/components/ui/actions-menu.tsx +11 -1
  145. package/src/contexts/chat-runtime-context.tsx +9 -0
  146. package/src/hooks/use-contact-submission.ts +2 -1
  147. package/src/hooks/use-self-fetch.ts +2 -1
  148. package/src/styles/index.css +1 -0
  149. package/src/styles/ods-page-shell.css +29 -0
  150. package/src/utils/embed-authed-fetch.ts +10 -0
  151. package/src/utils/embed-content-fetch.ts +30 -0
  152. package/dist/chunk-4DYXV3E7.cjs.map +0 -1
  153. package/dist/chunk-4YKGXZ7W.js.map +0 -1
  154. package/dist/chunk-5QJQAFTP.cjs.map +0 -1
  155. package/dist/chunk-722TTRIJ.cjs +0 -29
  156. package/dist/chunk-722TTRIJ.cjs.map +0 -1
  157. package/dist/chunk-7NJUPRN6.cjs.map +0 -1
  158. package/dist/chunk-BSOHVQ7X.js.map +0 -1
  159. package/dist/chunk-CYMMGPPV.js.map +0 -1
  160. package/dist/chunk-DAAFVOTL.cjs +0 -172
  161. package/dist/chunk-DAAFVOTL.cjs.map +0 -1
  162. package/dist/chunk-L4LAAA5Q.js.map +0 -1
  163. package/dist/chunk-MEAR2WHS.cjs.map +0 -1
  164. package/dist/chunk-OO4ZGDRO.js +0 -29
  165. package/dist/chunk-OO4ZGDRO.js.map +0 -1
  166. package/dist/chunk-OPSZNW3I.js.map +0 -1
  167. package/dist/chunk-QLWBBCI5.cjs.map +0 -1
  168. package/dist/chunk-T2URJAX2.js +0 -169
  169. package/dist/chunk-T2URJAX2.js.map +0 -1
  170. package/dist/chunk-T7AJBKZP.cjs.map +0 -1
  171. package/dist/chunk-WU6PMH5L.cjs.map +0 -1
  172. package/dist/chunk-XDGDCO3I.cjs.map +0 -1
  173. package/dist/chunk-Y2B6WDU6.cjs +0 -169
  174. package/dist/chunk-Y2B6WDU6.cjs.map +0 -1
  175. package/dist/chunk-ZMFT5XOB.js.map +0 -1
  176. package/dist/chunk-ZXZJSTNV.js +0 -172
  177. package/dist/chunk-ZXZJSTNV.js.map +0 -1
  178. /package/dist/{chunk-FLIWL6V4.js.map → chunk-62U6DFIT.js.map} +0 -0
  179. /package/dist/{chunk-GL5X6VVC.js.map → chunk-NITJSVUM.js.map} +0 -0
@@ -1,7 +1,7 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } } function _optionalChain(ops) { let lastAccessLHS = undefined; let value = ops[0]; let i = 1; while (i < ops.length) { const op = ops[i]; const fn = ops[i + 1]; i += 2; if ((op === 'optionalAccess' || op === 'optionalCall') && value == null) { return undefined; } if (op === 'access' || op === 'optionalAccess') { lastAccessLHS = value; value = fn(value); } else if (op === 'call' || op === 'optionalCall') { value = fn((...args) => value.call(lastAccessLHS, ...args)); lastAccessLHS = undefined; } } return value; }"use client";
2
2
 
3
3
 
4
- var _chunkT7AJBKZPcjs = require('./chunk-T7AJBKZP.cjs');
4
+ var _chunkGVSTRN36cjs = require('./chunk-GVSTRN36.cjs');
5
5
 
6
6
 
7
7
 
@@ -29,23 +29,21 @@ var _chunkT7AJBKZPcjs = require('./chunk-T7AJBKZP.cjs');
29
29
 
30
30
 
31
31
 
32
- var _chunk4DYXV3E7cjs = require('./chunk-4DYXV3E7.cjs');
33
32
 
33
+ var _chunkIAB7QACXcjs = require('./chunk-IAB7QACX.cjs');
34
34
 
35
- var _chunkY2B6WDU6cjs = require('./chunk-Y2B6WDU6.cjs');
36
35
 
37
36
 
38
37
 
39
-
40
- var _chunk7NJUPRN6cjs = require('./chunk-7NJUPRN6.cjs');
38
+ var _chunkLH7R56RRcjs = require('./chunk-LH7R56RR.cjs');
41
39
 
42
40
  // src/components/related-content/related-content-section.tsx
43
41
  var _react = require('react');
44
- _chunk4DYXV3E7cjs.init_pagination.call(void 0, );
42
+ _chunkIAB7QACXcjs.init_pagination.call(void 0, );
45
43
  var _jsxruntime = require('react/jsx-runtime');
46
44
  function DefaultLinkPropsProvider({ href, targetPlatform, children }) {
47
45
  if (!href) return children(null);
48
- const newTab = _chunk7NJUPRN6cjs.decideNewTab.call(void 0, { href, targetPlatform, currentSource: "" });
46
+ const newTab = _chunkLH7R56RRcjs.decideNewTab.call(void 0, { href, targetPlatform, currentSource: "" });
49
47
  return children(
50
48
  newTab ? { href, target: "_blank", rel: "noopener noreferrer" } : { href }
51
49
  );
@@ -57,29 +55,29 @@ function renderSkeletonForType(type, size, adminCampaignCard) {
57
55
  const legacySize = size === "sm" ? "sm" : "default";
58
56
  switch (type) {
59
57
  case "blog_post_existing":
60
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.BlogCardSkeleton, { size: legacySize });
58
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.BlogCardSkeleton, { size: legacySize });
61
59
  case "case_study":
62
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.CaseStudyCardSkeleton, { size: legacySize });
60
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.CaseStudyCardSkeleton, { size: legacySize });
63
61
  case "customer_interview":
64
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.CustomerInterviewCardSkeleton, { size: legacySize });
62
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.CustomerInterviewCardSkeleton, { size: legacySize });
65
63
  case "product_release":
66
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.ProductReleaseCardSkeleton, { size: size === "sm" ? "sm" : "lg" });
64
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.ProductReleaseCardSkeleton, { size: size === "sm" ? "sm" : "lg" });
67
65
  case "podcast":
68
66
  case "webinar":
69
67
  case "event":
70
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.ProgramCardSkeleton, { size: legacySize });
68
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.ProgramCardSkeleton, { size: legacySize });
71
69
  case "investor_update":
72
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.InvestorUpdateCardSkeleton, { size: legacySize });
70
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.InvestorUpdateCardSkeleton, { size: legacySize });
73
71
  case "onboarding_guide":
74
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk7NJUPRN6cjs.OnboardingGuideCardSkeleton, { size: size === "sm" ? "sm" : "catalog" });
72
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLH7R56RRcjs.OnboardingGuideCardSkeleton, { size: size === "sm" ? "sm" : "catalog" });
75
73
  case "what_i_shipped":
76
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.WhatIShippedCardSkeleton, {});
74
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.WhatIShippedCardSkeleton, {});
77
75
  case "marketing_campaign":
78
76
  return adminCampaignCard ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, adminCampaignCard.Skeleton, { size: legacySize }) : null;
79
77
  case "roadmap_item":
80
78
  case "delivery_item":
81
79
  case "internal_task":
82
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.RoadmapCardSkeleton, { size: legacySize });
80
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.RoadmapCardSkeleton, { size: legacySize });
83
81
  default:
84
82
  return null;
85
83
  }
@@ -99,17 +97,17 @@ function CardForType({
99
97
  const anchorAttrs = linkProps ? { target: linkProps.target, rel: linkProps.rel } : {};
100
98
  switch (type) {
101
99
  case "blog_post_existing":
102
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.BlogCard, { post: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
100
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.BlogCard, { post: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
103
101
  case "case_study":
104
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.CaseStudyCard, { study: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
102
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.CaseStudyCard, { study: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
105
103
  case "customer_interview":
106
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.CustomerInterviewCard, { interview: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
104
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.CustomerInterviewCard, { interview: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
107
105
  case "product_release": {
108
106
  const releaseSize = size === "sm" ? "sm" : "lg";
109
- const buildReleaseProps = _nullishCoalesce(_optionalChain([extras, 'optionalAccess', _5 => _5.buildProductReleaseCardProps]), () => ( _chunk4DYXV3E7cjs.buildProductReleaseCardProps));
107
+ const buildReleaseProps = _nullishCoalesce(_optionalChain([extras, 'optionalAccess', _5 => _5.buildProductReleaseCardProps]), () => ( _chunkIAB7QACXcjs.buildProductReleaseCardProps));
110
108
  const releaseProps = buildReleaseProps(item);
111
109
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
112
- _chunk4DYXV3E7cjs.ProductReleaseCard,
110
+ _chunkIAB7QACXcjs.ProductReleaseCard,
113
111
  {
114
112
  size: releaseSize,
115
113
  title: item.title,
@@ -121,18 +119,18 @@ function CardForType({
121
119
  );
122
120
  }
123
121
  case "podcast":
124
- return _optionalChain([extras, 'optionalAccess', _6 => _6.programConfigs, 'optionalAccess', _7 => _7.podcast]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.ProgramCard, { config: extras.programConfigs.podcast, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
122
+ return _optionalChain([extras, 'optionalAccess', _6 => _6.programConfigs, 'optionalAccess', _7 => _7.podcast]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.ProgramCard, { config: extras.programConfigs.podcast, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
125
123
  case "webinar":
126
- return _optionalChain([extras, 'optionalAccess', _8 => _8.programConfigs, 'optionalAccess', _9 => _9.webinar]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.ProgramCard, { config: extras.programConfigs.webinar, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
124
+ return _optionalChain([extras, 'optionalAccess', _8 => _8.programConfigs, 'optionalAccess', _9 => _9.webinar]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.ProgramCard, { config: extras.programConfigs.webinar, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
127
125
  case "event":
128
- return _optionalChain([extras, 'optionalAccess', _10 => _10.programConfigs, 'optionalAccess', _11 => _11.event]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.ProgramCard, { config: extras.programConfigs.event, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
126
+ return _optionalChain([extras, 'optionalAccess', _10 => _10.programConfigs, 'optionalAccess', _11 => _11.event]) ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.ProgramCard, { config: extras.programConfigs.event, item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs }) : null;
129
127
  case "investor_update":
130
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.InvestorUpdateCard, { update: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
128
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.InvestorUpdateCard, { update: item, size: legacySize, href, targetPlatform, placeholderUrl, ...anchorAttrs });
131
129
  case "onboarding_guide":
132
- return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk7NJUPRN6cjs.OnboardingGuideCard, { guide: item, size: size === "sm" ? "sm" : "catalog", href, targetPlatform, placeholderUrl, ...anchorAttrs });
130
+ return /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkLH7R56RRcjs.OnboardingGuideCard, { guide: item, size: size === "sm" ? "sm" : "catalog", href, targetPlatform, placeholderUrl, ...anchorAttrs });
133
131
  case "what_i_shipped":
134
132
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
135
- _chunk4DYXV3E7cjs.WhatIShippedCard,
133
+ _chunkIAB7QACXcjs.WhatIShippedCard,
136
134
  {
137
135
  entry: item,
138
136
  placeholderUrl,
@@ -145,7 +143,7 @@ function CardForType({
145
143
  case "delivery_item":
146
144
  case "internal_task":
147
145
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
148
- _chunk4DYXV3E7cjs.RoadmapCard,
146
+ _chunkIAB7QACXcjs.RoadmapCard,
149
147
  {
150
148
  item,
151
149
  href: _nullishCoalesce(href, () => ( "")),
@@ -165,15 +163,15 @@ function CardForType({
165
163
  function useGroupItems(type, refs, buildUrl) {
166
164
  const ids = refs.map((r) => r.id);
167
165
  const url = ids.length > 0 ? buildUrl(type, ids) : null;
168
- const { data, isLoading } = _chunkT7AJBKZPcjs.useSelfFetch.call(void 0, url);
169
- const items = data != null ? _chunk4DYXV3E7cjs.extractItems.call(void 0, data) : null;
166
+ const { data, isLoading } = _chunkGVSTRN36cjs.useSelfFetch.call(void 0, url);
167
+ const items = data != null ? _chunkIAB7QACXcjs.extractItems.call(void 0, data) : null;
170
168
  return { items, isLoading };
171
169
  }
172
170
  function gridClassFor(columns) {
173
171
  return columns === 3 ? "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6" : "grid grid-cols-1 sm:grid-cols-2 gap-6";
174
172
  }
175
173
  function resolveGroupConfig(type) {
176
- return _nullishCoalesce(_chunk4DYXV3E7cjs.CONTENT_REF_GROUPS[type], () => ( {
174
+ return _nullishCoalesce(_chunkIAB7QACXcjs.CONTENT_REF_GROUPS[type], () => ( {
177
175
  label: type,
178
176
  order: 999,
179
177
  layout: "grid",
@@ -208,7 +206,7 @@ function ContentGroup({
208
206
  const totalGroupPages = Math.max(1, Math.ceil(refs.length / GROUP_PAGE_SIZE));
209
207
  const safePage = Math.min(page, totalGroupPages);
210
208
  const visibleGroupRefs = refs.length > GROUP_PAGE_SIZE ? refs.slice((safePage - 1) * GROUP_PAGE_SIZE, safePage * GROUP_PAGE_SIZE) : refs;
211
- const groupPagination = totalGroupPages > 1 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunk4DYXV3E7cjs.Pagination, { currentPage: safePage, totalPages: totalGroupPages, onPageChange: setPage }) : null;
209
+ const groupPagination = totalGroupPages > 1 ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkIAB7QACXcjs.Pagination, { currentPage: safePage, totalPages: totalGroupPages, onPageChange: setPage }) : null;
212
210
  if (isLoading && !items) {
213
211
  const skeletons = visibleGroupRefs.map((r) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { children: renderSkeletonForType(type, cardSize, adminCampaignCard) }, r.id));
214
212
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-4", children: [
@@ -218,7 +216,7 @@ function ContentGroup({
218
216
  }
219
217
  if (!items || items.length === 0) return null;
220
218
  const itemById = new Map(
221
- items.map((it) => [_nullishCoalesce(_chunk4DYXV3E7cjs.extractItemId.call(void 0, type, it), () => ( String(_optionalChain([it, 'optionalAccess', _12 => _12.id])))), it])
219
+ items.map((it) => [_nullishCoalesce(_chunkIAB7QACXcjs.extractItemId.call(void 0, type, it), () => ( String(_optionalChain([it, 'optionalAccess', _12 => _12.id])))), it])
222
220
  );
223
221
  const cards = visibleGroupRefs.map((contentRef) => {
224
222
  const itemId = String(contentRef.id);
@@ -283,11 +281,11 @@ function RelatedContentSection({
283
281
  types: includeTypes !== void 0 ? includeTypes.join(",") : void 0,
284
282
  excludeTypes: excludeTypes && excludeTypes.length > 0 ? excludeTypes.join(",") : void 0
285
283
  };
286
- const authorUrl = contentRefs === void 0 && authorId && !suggestionsDisabled ? _chunkY2B6WDU6cjs.buildSuggestionUrl.call(void 0, "/api/related-content", {
284
+ const authorUrl = contentRefs === void 0 && authorId && !suggestionsDisabled ? _chunkIAB7QACXcjs.buildSuggestionUrl.call(void 0, "/api/related-content", {
287
285
  apiBaseUrl,
288
286
  extraParams: { authorId, ...typeFilterParams }
289
287
  }) : null;
290
- const suggestUrl = _nullishCoalesce(authorUrl, () => ( (contentRefs === void 0 && entityType && entityId !== void 0 && entityId !== null && entityId !== "" && !suggestionsDisabled ? _chunkY2B6WDU6cjs.buildSuggestionUrl.call(void 0, "/api/related-content", {
288
+ const suggestUrl = _nullishCoalesce(authorUrl, () => ( (contentRefs === void 0 && entityType && entityId !== void 0 && entityId !== null && entityId !== "" && !suggestionsDisabled ? _chunkIAB7QACXcjs.buildSuggestionUrl.call(void 0, "/api/related-content", {
291
289
  apiBaseUrl,
292
290
  entityType,
293
291
  entityId,
@@ -304,9 +302,9 @@ function RelatedContentSection({
304
302
  () => !suggestionsDisabled && initialItems ? { refs: initialItems } : void 0,
305
303
  [initialItems, suggestionsDisabled]
306
304
  );
307
- const { data, isLoading } = _chunkT7AJBKZPcjs.useSelfFetch.call(void 0, suggestUrl, { initialData });
305
+ const { data, isLoading } = _chunkGVSTRN36cjs.useSelfFetch.call(void 0, suggestUrl, { initialData });
308
306
  const effectiveBuildListUrl = _react.useMemo.call(void 0,
309
- () => _nullishCoalesce(buildListUrl2, () => ( ((type, ids) => _chunk4DYXV3E7cjs.buildListUrl.call(void 0, type, ids, apiBaseUrl)))),
307
+ () => _nullishCoalesce(buildListUrl2, () => ( ((type, ids) => _chunkIAB7QACXcjs.buildListUrl.call(void 0, type, ids, apiBaseUrl)))),
310
308
  [buildListUrl2, apiBaseUrl]
311
309
  );
312
310
  const refs = _nullishCoalesce(_nullishCoalesce(contentRefs, () => ( _optionalChain([data, 'optionalAccess', _14 => _14.refs]))), () => ( []));
@@ -331,12 +329,12 @@ function RelatedContentSection({
331
329
  if (!grouped[ref.type]) grouped[ref.type] = [];
332
330
  grouped[ref.type].push(ref);
333
331
  }
334
- let orderedTypes = _chunk4DYXV3E7cjs.orderContentRefTypes.call(void 0, Object.keys(grouped));
332
+ let orderedTypes = _chunkIAB7QACXcjs.orderContentRefTypes.call(void 0, Object.keys(grouped));
335
333
  if (entityType) {
336
- const canonicalEntityType = _chunk4DYXV3E7cjs.canonicalContentRefType.call(void 0, entityType);
337
- const sameType = orderedTypes.filter((t) => _chunk4DYXV3E7cjs.canonicalContentRefType.call(void 0, t) === canonicalEntityType);
334
+ const canonicalEntityType = _chunkIAB7QACXcjs.canonicalContentRefType.call(void 0, entityType);
335
+ const sameType = orderedTypes.filter((t) => _chunkIAB7QACXcjs.canonicalContentRefType.call(void 0, t) === canonicalEntityType);
338
336
  if (sameType.length > 0) {
339
- orderedTypes = [...sameType, ...orderedTypes.filter((t) => _chunk4DYXV3E7cjs.canonicalContentRefType.call(void 0, t) !== canonicalEntityType)];
337
+ orderedTypes = [...sameType, ...orderedTypes.filter((t) => _chunkIAB7QACXcjs.canonicalContentRefType.call(void 0, t) !== canonicalEntityType)];
340
338
  }
341
339
  }
342
340
  return /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-8", children: [
@@ -352,7 +350,7 @@ function RelatedContentSection({
352
350
  LinkProvider,
353
351
  extras,
354
352
  adminCampaignCard,
355
- heading: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "font-['Azeret_Mono'] text-[14px] font-semibold uppercase text-ods-text-secondary tracking-wider", children: _chunk4DYXV3E7cjs.getContentRefLabelOrTitleCase.call(void 0, type) })
353
+ heading: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "font-['Azeret_Mono'] text-[14px] font-semibold uppercase text-ods-text-secondary tracking-wider", children: _chunkIAB7QACXcjs.getContentRefLabelOrTitleCase.call(void 0, type) })
356
354
  },
357
355
  type
358
356
  ))
@@ -363,4 +361,4 @@ function RelatedContentSection({
363
361
 
364
362
 
365
363
  exports.GROUP_PAGE_SIZE = GROUP_PAGE_SIZE; exports.RelatedContentSection = RelatedContentSection;
366
- //# sourceMappingURL=chunk-WU6PMH5L.cjs.map
364
+ //# sourceMappingURL=chunk-MYJKC56A.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-MYJKC56A.cjs","../src/components/related-content/related-content-section.tsx"],"names":["buildListUrl"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-MYJKC56A.cjs","sourcesContent":[null,"\"use client\";\n\n/**\n * RelatedContentSection\n *\n * Renders content references grouped by type using the canonical card\n * components. MOVED from the hub (`components/shared/related-content-card.tsx`)\n * so any consuming app can embed it; the hub keeps a thin wrapper that\n * pre-binds its host-specific injections (nav hook, URL recomposition,\n * program configs, admin campaign card).\n *\n * THREE data modes (precedence top-down):\n * 1. CONTROLLED — `contentRefs` provided (even `[]`): render exactly those\n * refs, no suggestion fetch (the original investor-update behavior).\n * 2. SUGGESTION — `entityType` + `entityId` provided: self-fetch\n * `GET {apiBaseUrl}/api/related-content?entityType&entityId[&count][&excludeTypes]`\n * (the generic 5-tier engine's second web service). `minResults` maps to\n * `count`; absent → param not sent (server default applies). Each ref\n * carries a `reason` (data-only — never rendered, matching the\n * FaqSection/FaqWithReason precedent).\n * 3. SSR-HYDRATED suggestion — also pass `initialItems` (the server page\n * called the engine directly); the first client fetch is skipped per the\n * `useSelfFetch` initialData contract.\n *\n * Group layout (list vs grid) + card size (lg vs default) come from\n * `CONTENT_REF_GROUPS` in `../../utils/content-ref-groups` — single source of\n * truth, no per-type logic in this file. Skeletons come from\n * `renderSkeletonForType` so the placeholder height matches the loaded card\n * exactly (zero layout shift on resolve).\n *\n * One API call per content type via the shared list-URL builder\n * (`buildListUrl` — injectable; defaults to the lib's byte-parity-tested\n * builder prefixed with `apiBaseUrl`). Fetching uses `useSelfFetch` (plain\n * fetch, NO react-query) so third-party embedders need no QueryClientProvider;\n * cards are imported via DEEP module paths (not the chat barrel) so this\n * chunk never reaches `@tanstack/react-query`.\n *\n * LOCKSTEP NOTE: this file's per-type card/skeleton dispatch is the SIZED\n * sibling of the chat-side `CHAT_CARD_REGISTRY` (`../chat/entity-cards/\n * dispatch.tsx`), which renders compact `size='sm'` cards wired to the chat\n * runtime. Two dispatchers by design — when registering a new fetch-mode\n * content type, add it BOTH there and here (cards + skeleton + list URL).\n */\n\nimport React, { useEffect, useMemo, useRef, useState } from 'react';\nimport {\n CONTENT_REF_GROUPS,\n getContentRefLabelOrTitleCase,\n orderContentRefTypes,\n type ContentRefGroupConfig,\n} from '../../utils/content-ref-groups';\nimport type { ContentRef, ContentRefWithReason } from '../../types/content-ref';\nimport { useSelfFetch } from '../../hooks/use-self-fetch';\nimport { Pagination } from '../pagination';\nimport { extractItems, extractItemId } from '../../utils/extract-items';\nimport { buildListUrl as libBuildListUrl, canonicalContentRefType } from '../../utils/list-url';\nimport { buildSuggestionUrl } from '../../utils/suggestion-url';\nimport { decideNewTab } from '../chat/utils/decide-new-tab';\n// DEEP card imports — NOT the `../chat` barrel (the barrel statically reaches\n// @tanstack/react-query via embeddable-chat + its hooks). Deep paths keep this\n// component's SOURCE graph react-query-free. Note: tsup's shared-chunk\n// splitting may still colocate the cards with chat hooks in one dist chunk\n// (react-query is a required peerDep, so resolution always succeeds) — the\n// guarantee that matters here is the RUNTIME one: nothing on this path ever\n// instantiates a QueryClient, so embedders need NO QueryClientProvider.\nimport { BlogCard, BlogCardSkeleton } from '../chat/entity-cards/blog-card';\nimport { WhatIShippedCard, WhatIShippedCardSkeleton } from '../chat/entity-cards/what-i-shipped-card';\nimport { CaseStudyCard, CaseStudyCardSkeleton } from '../chat/entity-cards/case-study-card';\nimport { CustomerInterviewCard, CustomerInterviewCardSkeleton } from '../chat/entity-cards/customer-interview-card';\nimport { ProductReleaseCard, ProductReleaseCardSkeleton } from '../chat/entity-cards/product-release-card';\nimport { buildProductReleaseCardProps } from '../chat/entity-cards/product-release-card-defaults';\nimport { ProgramCard, ProgramCardSkeleton } from '../chat/entity-cards/program-card';\nimport { InvestorUpdateCard, InvestorUpdateCardSkeleton } from '../chat/entity-cards/investor-update-card';\nimport { OnboardingGuideCard, OnboardingGuideCardSkeleton } from '../chat/entity-cards/onboarding-guide-card';\nimport { RoadmapCard, RoadmapCardSkeleton } from '../chat/entity-cards/roadmap-card';\n// Type-only — erased at build, no runtime dependency on the dispatch module.\nimport type { ChatCardDispatchExtras } from '../chat/entity-cards/dispatch';\n\ntype CardSize = 'lg' | 'default' | 'sm';\n\n/** Anchor prop bundle the per-card link surface receives — same shape the\n * hub's `useNavLink` returns and the chat dispatcher's anchor builders\n * produce. `null` = non-anchor mode (no URL). */\nexport interface CardLinkAnchorProps {\n href: string;\n target?: '_blank';\n rel?: 'noopener noreferrer';\n onClick?: (e: React.MouseEvent<HTMLAnchorElement>) => void;\n}\n\n/** Render-prop component injection for the navigation decision — keeps hook\n * calls legal (hooks live INSIDE the injected component; `CardForType`\n * itself calls zero hooks). The hub injects a `useNavLink`-backed provider;\n * the default is hook-free (pure `decideNewTab`). MUST be defined at module\n * scope by hosts — an inline arrow would remount every card each render. */\nexport interface CardLinkProviderProps {\n href: string | null;\n targetPlatform: string | null;\n children: (linkProps: CardLinkAnchorProps | null) => React.ReactElement | null;\n}\nexport type CardLinkProvider = React.ComponentType<CardLinkProviderProps>;\n\n/** Default link provider for standalone embeds: relative/-same-origin hrefs\n * stay same-tab, cross-origin pops a new tab (pure `decideNewTab` with no\n * platform context — `currentSource: ''` falls through to the origin\n * check). No router integration, no hooks. */\nfunction DefaultLinkPropsProvider({ href, targetPlatform, children }: CardLinkProviderProps): React.ReactElement | null {\n if (!href) return children(null);\n const newTab = decideNewTab({ href, targetPlatform, currentSource: '' });\n return children(\n newTab\n ? { href, target: '_blank', rel: 'noopener noreferrer' }\n : { href },\n );\n}\n\n/** Default href resolution: trust the ref's stored url/targetPlatform as the\n * API composed them. The hub overrides this with its `buildContentURL`\n * re-composition so dev gets localhost and prod gets platform domains. */\nfunction defaultResolveHref(ref: ContentRef): { href: string | null; targetPlatform: string | null } {\n return { href: ref.url || null, targetPlatform: ref.targetPlatform ?? null };\n}\n\n/** Host-injected renderer pair for the admin-only `marketing_campaign` type.\n * Absent (every non-hub embed) → the type renders nothing (its list URL\n * hits `/api/admin`, unreachable outside the hub anyway). */\nexport interface AdminCampaignCardSlot {\n Card: React.ComponentType<{ campaign: any }>;\n Skeleton: React.ComponentType<{ size?: 'default' | 'sm' }>;\n}\n\n/**\n * Per-type skeleton dispatch — returns the SAME colocated skeleton the\n * resolved card renders, sized to match (zero layout shift on resolve).\n * The chat-side `CHAT_CARD_REGISTRY` already does this via\n * `entry.skeleton()`; this surface exposes the same discipline to the\n * related-content rail.\n */\nfunction renderSkeletonForType(\n type: string,\n size: CardSize,\n adminCampaignCard?: AdminCampaignCardSlot,\n): React.ReactNode {\n // Most card skeletons accept only `{default, sm}`. `'lg'` collapses to\n // `'default'`. ProductReleaseCardSkeleton uses lg/sm pair.\n const legacySize: 'default' | 'sm' = size === 'sm' ? 'sm' : 'default';\n switch (type) {\n case 'blog_post_existing':\n return <BlogCardSkeleton size={legacySize} />;\n case 'case_study':\n return <CaseStudyCardSkeleton size={legacySize} />;\n case 'customer_interview':\n return <CustomerInterviewCardSkeleton size={legacySize} />;\n case 'product_release':\n return <ProductReleaseCardSkeleton size={size === 'sm' ? 'sm' : 'lg'} />;\n case 'podcast':\n case 'webinar':\n case 'event':\n return <ProgramCardSkeleton size={legacySize} />;\n case 'investor_update':\n return <InvestorUpdateCardSkeleton size={legacySize} />;\n case 'onboarding_guide':\n // The rich catalog variant (hero + author grid, clamped description) —\n // the step-numbered 'default' variant is for the guide detail page's\n // \"More in section\" rail, not this full-width row.\n return <OnboardingGuideCardSkeleton size={size === 'sm' ? 'sm' : 'catalog'} />;\n case 'what_i_shipped':\n // Matches the WhatIShippedCard (AdminContentCard 3:2) shape.\n return <WhatIShippedCardSkeleton />;\n case 'marketing_campaign':\n return adminCampaignCard ? <adminCampaignCard.Skeleton size={legacySize} /> : null;\n case 'roadmap_item':\n case 'delivery_item':\n case 'internal_task':\n return <RoadmapCardSkeleton size={legacySize} />;\n default:\n return null;\n }\n}\n\n/**\n * Per-type card dispatch — renders the right card with the right size.\n * Sized cards (`'lg'` / `'default'`) are unique to this rail — the chat\n * dispatcher only renders `'sm'`, so we go directly through the per-type\n * cards here.\n *\n * PURE FUNCTION COMPONENT WITH ZERO HOOK CALLS: the placeholder comes from a\n * plain `extras.buildOgPlaceholderUrl` call (the chat `dispatch.tsx`\n * pattern) and the anchor-prop bundle arrives via the `LinkProvider`\n * render-prop from the parent — so per-card hook legality is owned by the\n * injected provider component, not by this switch.\n *\n * `href` comes from the host's `resolveHref(ref)` (hub: live\n * `buildContentURL` recomposition; default: the ref's stored url).\n */\nfunction CardForType({\n type,\n item,\n size,\n href,\n targetPlatform,\n linkProps,\n extras,\n adminCampaignCard,\n}: {\n type: string;\n item: any;\n size: CardSize;\n href: string;\n targetPlatform: string | null;\n linkProps: CardLinkAnchorProps | null;\n extras?: ChatCardDispatchExtras;\n adminCampaignCard?: AdminCampaignCardSlot;\n}): React.ReactNode {\n // Most card variants accept only `{default, sm}`. `'lg'` collapses to\n // `'default'` for those. ProductReleaseCard uses its own lg/sm pair.\n const legacySize: 'default' | 'sm' = size === 'sm' ? 'sm' : 'default';\n // OG placeholder URL — injected into the pure-presentation cards so they\n // render a branded fallback when the row's featured image is null. Plain\n // function call (NOT a hook). Title is the universal field across all card\n // item shapes used here.\n const placeholderUrl =\n extras?.buildOgPlaceholderUrl?.((item?.title as string | undefined) ?? '') ?? undefined;\n\n // Top-level target/rel for cards that take them as separate props\n // (BlogCard, CaseStudyCard, …). ProductReleaseCard takes the bundle as a\n // single `anchorProps={...}` and uses `linkProps` directly. When the host\n // didn't surface a URL, `linkProps` is null and the card stays in\n // non-anchor mode.\n const anchorAttrs: Pick<CardLinkAnchorProps, 'target' | 'rel'> = linkProps\n ? { target: linkProps.target, rel: linkProps.rel }\n : {};\n\n switch (type) {\n case 'blog_post_existing':\n return <BlogCard post={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />;\n case 'case_study':\n return <CaseStudyCard study={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />;\n case 'customer_interview':\n return <CustomerInterviewCard interview={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />;\n case 'product_release': {\n // Anchor-prop pattern: build product-release lg-variant props from the\n // shared `buildProductReleaseCardProps` so this rail and the /releases\n // catalog page render byte-identically. The card wraps in\n // `<a {...anchorProps}>` ONLY when `anchorProps.href` is set — pass\n // `undefined` (not an empty object) when href is empty so the card\n // stays in non-anchor mode without rendering a dead <a> tag.\n const releaseSize = size === 'sm' ? 'sm' : 'lg';\n const buildReleaseProps = extras?.buildProductReleaseCardProps ?? buildProductReleaseCardProps;\n const releaseProps = buildReleaseProps(item);\n return (\n <ProductReleaseCard\n size={releaseSize}\n title={item.title}\n summary={item.summary}\n version={item.version}\n {...releaseProps}\n anchorProps={linkProps ?? undefined}\n />\n );\n }\n case 'podcast':\n return extras?.programConfigs?.podcast\n ? <ProgramCard config={extras.programConfigs.podcast} item={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />\n : null;\n case 'webinar':\n return extras?.programConfigs?.webinar\n ? <ProgramCard config={extras.programConfigs.webinar} item={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />\n : null;\n case 'event':\n return extras?.programConfigs?.event\n ? <ProgramCard config={extras.programConfigs.event} item={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />\n : null;\n case 'investor_update':\n return <InvestorUpdateCard update={item} size={legacySize} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />;\n case 'onboarding_guide':\n // Catalog variant (see skeleton note) — full-width rich card with a\n // line-clamped description instead of the step-numbered rail card.\n return <OnboardingGuideCard guide={item} size={size === 'sm' ? 'sm' : 'catalog'} href={href} targetPlatform={targetPlatform} placeholderUrl={placeholderUrl} {...anchorAttrs} />;\n case 'what_i_shipped':\n // THE single What I Shipped card — same lib component the people-hub\n // dashboard renders, so the card is identical in the rail and the\n // dashboard. `anchorProps` makes the whole card a click-through link\n // (rail is read-only — no owner actions).\n return (\n <WhatIShippedCard\n entry={item}\n placeholderUrl={placeholderUrl}\n // Only pass anchorProps when there's a REAL href — a fallback object\n // with `href: undefined` is still truthy and would make WhatIShippedCard\n // wrap the card in a dead <a> (no URL). Mirrors the ProductReleaseCard\n // `linkProps ?? undefined` pattern above.\n anchorProps={\n linkProps ??\n (href ? ({ href, ...anchorAttrs } as React.AnchorHTMLAttributes<HTMLAnchorElement>) : undefined)\n }\n />\n );\n case 'marketing_campaign':\n return adminCampaignCard ? <adminCampaignCard.Card campaign={item} /> : null;\n case 'roadmap_item':\n case 'delivery_item':\n case 'internal_task':\n return (\n <RoadmapCard\n item={item}\n href={href ?? ''}\n targetPlatform={targetPlatform}\n userVote={null}\n onVote={() => {}}\n size={legacySize}\n cardType={type as 'roadmap_item' | 'delivery_item' | 'internal_task'}\n {...anchorAttrs}\n />\n );\n default:\n return null;\n }\n}\n\n// =============================================================================\n// Fetch all items for a type in ONE server-sorted call, via the injectable\n// list-URL builder. `useSelfFetch` (URL = cache key) replaces the hub's old\n// react-query usage: `enabled` ≙ `url === null`, `!res.ok`/network error ≙\n// `error → items null → group renders nothing`. Accepted deltas vs\n// react-query: no retry/backoff, no focus refetch, no cross-mount cache.\n// =============================================================================\n\nfunction useGroupItems(\n type: string,\n refs: ContentRef[],\n buildUrl: (type: string, ids: string[]) => string | null,\n) {\n const ids = refs.map((r) => r.id);\n const url = ids.length > 0 ? buildUrl(type, ids) : null;\n const { data, isLoading } = useSelfFetch<unknown>(url);\n const items = data != null ? extractItems(data) : null;\n return { items, isLoading };\n}\n\n// =============================================================================\n// Per-group renderer — one API call, server-sorted, then render cards via the\n// dispatcher with per-type skeletons + per-type layout from CONTENT_REF_GROUPS.\n// =============================================================================\n\n/** Map columns prop → tailwind grid class. Only consulted for grid-layout\n * groups; list-layout groups stack vertically. */\nfunction gridClassFor(columns: 2 | 3): string {\n return columns === 3\n ? 'grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6'\n : 'grid grid-cols-1 sm:grid-cols-2 gap-6';\n}\n\n/** Resolve the group config for a type, falling back to a grid layout with\n * the default card size for unregistered types so the section still renders\n * rather than silently dropping them. The `label` field on the fallback is\n * intentionally a placeholder — the section heading goes through\n * `getContentRefLabelOrTitleCase(type)` instead so cross-surface labels\n * stay consistent between this rail and the investor-email builder. */\nfunction resolveGroupConfig(type: string): ContentRefGroupConfig {\n return CONTENT_REF_GROUPS[type] ?? {\n label: type,\n order: 999,\n layout: 'grid',\n gridSize: 'default',\n };\n}\n\n/** Items per page within one type group. Groups larger than this paginate\n * with the standard Pagination control (NO nested scrolling — a bounded\n * scrollbox inside the page traps wheel events and hides the sections\n * below it). MUST stay at or above the largest suggestion fill\n * (RELATED_SAME_TYPE_COUNT in the hub's lib/constants/suggestions.ts) so\n * current rails never paginate — only genuinely big groups (author pages)\n * do. Exported through the subpath barrel for the hub's module-load\n * assertion of that relation (entity-suggestion-sections.tsx). */\nexport const GROUP_PAGE_SIZE = 12;\n\nfunction ContentGroup({\n type,\n refs,\n columns,\n buildUrl,\n resolveHref,\n LinkProvider,\n extras,\n adminCampaignCard,\n heading,\n}: {\n type: string;\n refs: ContentRef[];\n columns: 2 | 3;\n buildUrl: (type: string, ids: string[]) => string | null;\n resolveHref: (ref: ContentRef) => { href: string | null; targetPlatform: string | null };\n LinkProvider: CardLinkProvider;\n extras?: ChatCardDispatchExtras;\n adminCampaignCard?: AdminCampaignCardSlot;\n /** Group heading, rendered INSIDE the group so a group that resolves to\n * nothing (fetch miss / unsupported type / missing program config) drops\n * its heading too — no orphaned titles. */\n heading: React.ReactNode;\n}) {\n const { items, isLoading } = useGroupItems(type, refs, buildUrl);\n const config = resolveGroupConfig(type);\n const isListLayout = config.layout === 'list';\n const cardSize = config.gridSize;\n\n // Per-group pagination for big groups (author pages): GROUP_PAGE_SIZE items\n // per page with the standard Pagination control below the group. Client-side\n // slicing — useGroupItems already fetched every row in one batched call, so\n // page flips are instant. Hooks live above every early return (file\n // convention). Page is clamped so a shrinking refs array (suggestion\n // refetch) can never strand the view past the last page, and RESET when the\n // ref set actually changes (shrink→grow must not return to a stale page).\n const [page, setPage] = useState(1);\n const refsKey = refs.map((r) => r.id).join('|');\n const prevRefsKeyRef = useRef(refsKey);\n useEffect(() => {\n if (prevRefsKeyRef.current !== refsKey) {\n prevRefsKeyRef.current = refsKey;\n setPage(1);\n }\n }, [refsKey]);\n const totalGroupPages = Math.max(1, Math.ceil(refs.length / GROUP_PAGE_SIZE));\n const safePage = Math.min(page, totalGroupPages);\n const visibleGroupRefs =\n refs.length > GROUP_PAGE_SIZE\n ? refs.slice((safePage - 1) * GROUP_PAGE_SIZE, safePage * GROUP_PAGE_SIZE)\n : refs;\n const groupPagination =\n totalGroupPages > 1 ? (\n <Pagination currentPage={safePage} totalPages={totalGroupPages} onPageChange={setPage} />\n ) : null;\n\n // Skeleton gate: `isLoading && !items` — SSR HTML and the client's first\n // paint render identical skeletons (useSelfFetch starts isLoading=true on\n // both sides), and once items exist they are never replaced by skeletons.\n if (isLoading && !items) {\n const skeletons = visibleGroupRefs.map((r) => (\n <div key={r.id}>{renderSkeletonForType(type, cardSize, adminCampaignCard)}</div>\n ));\n return (\n <div className=\"space-y-4\">\n {heading}\n {isListLayout ? (\n <div className=\"space-y-4\">{skeletons}</div>\n ) : (\n <div className={gridClassFor(columns)}>{skeletons}</div>\n )}\n </div>\n );\n }\n\n if (!items || items.length === 0) return null;\n\n // Index fetched rows by id, then render in REF order — refs carry the\n // intended sequence (suggestion mode: the engine's tier order, so\n // same-platform/tag-matched items lead; controlled mode: the curated\n // display_order). The list APIs return rows date-sorted, which would\n // otherwise scramble that ordering (same-platform items sinking below\n // newer cross-platform ones).\n // Shared extractor (NOT raw `.id`) — some API shapes key items differently\n // (e.g. external_id types); raw access would silently drop valid items.\n const itemById = new Map(\n (items as any[]).map((it) => [extractItemId(type, it) ?? String((it as any)?.id), it]),\n );\n\n const cards = visibleGroupRefs\n .map((contentRef) => {\n const itemId = String(contentRef.id);\n const item = itemById.get(itemId);\n if (!item) return null;\n // Re-compose the URL via the host's resolver (hub: buildContentURL so\n // dev gets localhost and prod the right platform domain; default: the\n // ref's stored url as the API composed it).\n const resolved = resolveHref(contentRef);\n const href = resolved.href ?? '';\n const targetPlatform = resolved.targetPlatform ?? contentRef.targetPlatform ?? null;\n return (\n <div key={itemId}>\n <LinkProvider href={href || null} targetPlatform={targetPlatform}>\n {(linkProps) => (\n <CardForType\n type={type}\n item={item}\n size={cardSize}\n href={href}\n targetPlatform={targetPlatform}\n linkProps={linkProps}\n extras={extras}\n adminCampaignCard={adminCampaignCard}\n />\n )}\n </LinkProvider>\n </div>\n );\n })\n .filter(Boolean);\n\n if (cards.length === 0) {\n // Current PAGE resolved zero cards (rows deleted between the ref fetch\n // and the group fetch, or a stricter list-API gate dropped them). When a\n // pager exists the user must keep the controls to navigate back —\n // dropping the whole group would strand them. A genuinely empty group\n // (no pager) still vanishes with its heading.\n if (groupPagination) {\n return (\n <div className=\"space-y-4\">\n {heading}\n {groupPagination}\n </div>\n );\n }\n return null;\n }\n\n return (\n <div className=\"space-y-4\">\n {heading}\n {isListLayout ? (\n <div className=\"space-y-4\">{cards}</div>\n ) : (\n <div className={gridClassFor(columns)}>{cards}</div>\n )}\n {groupPagination}\n </div>\n );\n}\n\n// =============================================================================\n// Main component\n// =============================================================================\n\ninterface RelatedContentResponse {\n refs: ContentRefWithReason[];\n}\n\nexport interface RelatedContentSectionProps {\n /** CONTROLLED mode (the original behavior). When defined — even `[]` — no\n * suggestion fetch runs and exactly these refs render. */\n contentRefs?: ContentRef[];\n /** SUGGESTION mode (with `entityId`): self-fetch suggestions for this host\n * entity from `{apiBaseUrl}/api/related-content`. Ignored when\n * `contentRefs` is provided. */\n entityType?: string;\n entityId?: number | string;\n /** AUTHOR mode: self-fetch ALL published content authored by this profile\n * from `{apiBaseUrl}/api/related-content?authorId=…` (grouped per type,\n * endless within each group). Ignored when `contentRefs` is provided;\n * takes precedence over the entityType/entityId suggestion scope.\n * SSR-hydrate via `initialItems`, same as suggestion mode. */\n authorId?: string;\n /** Maps to the suggestion API's `count` param — the PER-TYPE fill target\n * for every candidate type EXCEPT the host's own. Absent → param not sent\n * (server default applies). */\n minResults?: number;\n /** Maps to the suggestion API's `sameTypeCount` param — the budget for the\n * candidate type MATCHING the host's own `entityType` (same-type boost:\n * a blog post's rail leads with more blog posts). Absent → param not\n * sent (host's type uses the server's `count`). */\n sameTypeMinResults?: number;\n /** SSR hydrate for suggestion mode — the server page ran the engine and\n * drills the refs here; the first client fetch is skipped (useSelfFetch\n * initialData contract). */\n initialItems?: ContentRefWithReason[];\n /** Section title (default: \"Related Content\") */\n title?: string;\n /**\n * Grid columns at desktop. 2 = denser cards / wider summary (original\n * investor-update layout); 3 = more cards per row for dashboards.\n * Only consulted for grid-layout groups. Default: 2.\n */\n columns?: 2 | 3;\n /**\n * ContentRef.type values to exclude. Honored in ALL modes — controlled\n * mode post-filters (original behavior); suggestion mode ALSO forwards the\n * list verbatim as the API's `excludeTypes=` param so excluded types never\n * consume engine fill slots (`minResults` stays honored). The subtraction\n * happens SERVER-side — this component never mirrors the hub's candidate\n * list.\n */\n excludeTypes?: string[];\n /**\n * SUGGESTION-mode allow-list (rail vocabulary): which content types\n * participate in this rail. Sent verbatim as the API's `types=` param —\n * the SERVER intersects it with its own allowed candidate set, and\n * platform policy gates (e.g. internal-only types) ALWAYS win: the client\n * cannot request its way past them. Absent → all server-side candidates.\n */\n includeTypes?: string[];\n /** Fetch-URL prefix for third-party embeds / reverse proxies\n * ('' = same-origin). Applied to BOTH the suggestion fetch and the\n * default per-group list fetches. */\n apiBaseUrl?: string;\n /** Host injection bundle — REUSES the chat dispatcher's\n * `ChatCardDispatchExtras` (programConfigs, buildOgPlaceholderUrl,\n * buildProductReleaseCardProps override). Program groups render nothing\n * when their config is absent. */\n extras?: ChatCardDispatchExtras;\n /** Hub injects its `buildContentURL` recomposition; default uses the\n * ref's stored `url`/`targetPlatform` as the API composed them. */\n resolveHref?: (ref: ContentRef) => { href: string | null; targetPlatform: string | null };\n /** Hub injects its registry-driven entity-list-api builder; default = the\n * lib's `buildListUrl(type, ids, apiBaseUrl)`. */\n buildListUrl?: (type: string, ids: string[]) => string | null;\n /** Hub injects a `useNavLink`-backed render-prop provider; default = pure\n * anchor via `decideNewTab`. MUST be a module-scope component. */\n LinkProvider?: CardLinkProvider;\n /** Renderer pair for the admin-only `marketing_campaign` type. Absent →\n * the type renders nothing. */\n adminCampaignCard?: AdminCampaignCardSlot;\n /** When true, render the section shell (title + an empty-state line) even\n * with ZERO refs, instead of returning null. Default false (the original\n * behavior — empty rail = no shell). Opt-in per host page (e.g. people-hub's\n * \"What I Shipped\", where the section should always be present). */\n showWhenEmpty?: boolean;\n /** Empty-state copy shown under the title when `showWhenEmpty` and no refs.\n * Default: \"No related content yet.\" */\n emptyStateText?: string;\n /** Custom empty-state node (e.g. a hub `<EmptyState/>`) rendered under the\n * title when `showWhenEmpty` and there are no refs — overrides\n * `emptyStateText`. Lets a host match its canonical empty state. */\n emptyState?: React.ReactNode;\n}\n\nexport function RelatedContentSection({\n contentRefs,\n entityType,\n entityId,\n authorId,\n minResults,\n sameTypeMinResults,\n includeTypes,\n initialItems,\n title = 'Related Content',\n columns = 2,\n excludeTypes,\n apiBaseUrl = '',\n extras,\n resolveHref = defaultResolveHref,\n buildListUrl,\n LinkProvider = DefaultLinkPropsProvider,\n adminCampaignCard,\n showWhenEmpty = false,\n emptyStateText = 'No related content yet.',\n emptyState,\n}: RelatedContentSectionProps) {\n // ── Hooks above EVERY early return (the original `if (!contentRefs.length)\n // return null` guard moved below them). ──\n\n // Suggestion-mode fetch URL — null in controlled mode (contentRefs defined,\n // even []) or when the entity scope is incomplete.\n // `includeTypes: []` is an explicit \"nothing participates\" — skip the fetch\n // entirely (an empty-string `types=` param would be dropped by the URL\n // builder and read server-side as \"all candidates\") AND ignore SSR refs.\n const suggestionsDisabled = includeTypes?.length === 0;\n // Shared type-filter params — one spelling for both fetch modes so a future\n // normalization (trim/dedupe) can't diverge between them.\n const typeFilterParams = {\n types: includeTypes !== undefined ? includeTypes.join(',') : undefined,\n excludeTypes: excludeTypes && excludeTypes.length > 0 ? excludeTypes.join(',') : undefined,\n };\n // AUTHOR mode beats suggestion mode: when `authorId` is set the rail lists\n // everything that profile authored (the server returns ALL, no count).\n const authorUrl =\n contentRefs === undefined && authorId && !suggestionsDisabled\n ? buildSuggestionUrl('/api/related-content', {\n apiBaseUrl,\n extraParams: { authorId, ...typeFilterParams },\n })\n : null;\n const suggestUrl =\n authorUrl ??\n (contentRefs === undefined &&\n entityType &&\n entityId !== undefined &&\n entityId !== null &&\n entityId !== '' &&\n !suggestionsDisabled\n ? buildSuggestionUrl('/api/related-content', {\n apiBaseUrl,\n entityType,\n entityId,\n count: minResults,\n extraParams: {\n sameTypeCount: sameTypeMinResults !== undefined ? String(sameTypeMinResults) : undefined,\n ...typeFilterParams,\n },\n })\n : null);\n // Memoize the initialData wrapper — useSelfFetch re-syncs on [initialData],\n // and a fresh per-render object would loop setState under re-rendering\n // parents (the latent FaqSection bug, fixed there in the same change).\n const initialData = useMemo<RelatedContentResponse | undefined>(\n // An explicitly disabled rail (includeTypes: []) must ignore SSR-hydrated\n // refs too — otherwise useSelfFetch(null, {initialData}) keeps serving\n // initialItems and the \"nothing participates\" contract silently breaks.\n () => (!suggestionsDisabled && initialItems ? { refs: initialItems } : undefined),\n [initialItems, suggestionsDisabled],\n );\n const { data, isLoading } = useSelfFetch<RelatedContentResponse>(suggestUrl, { initialData });\n\n // Default group fetcher: the lib's byte-parity-tested builder, prefixed for\n // embeds. Memoized so group-fetch URLs stay value-stable across renders.\n const effectiveBuildListUrl = useMemo(\n () => buildListUrl ?? ((type: string, ids: string[]) => libBuildListUrl(type, ids, apiBaseUrl)),\n [buildListUrl, apiBaseUrl],\n );\n\n const refs: ContentRef[] = contentRefs ?? data?.refs ?? [];\n\n // Per-consumer type gating — drops refs whose `type` is in the exclude\n // list. In suggestion mode the server already subtracted these (the param\n // is forwarded above); the client filter stays as an idempotent guard and\n // IS the mechanism in controlled mode (original behavior).\n const exclude = new Set(excludeTypes || []);\n const visibleRefs = exclude.size > 0 ? refs.filter((r) => !exclude.has(r.type)) : refs;\n // Zero refs (still loading in suggestion mode, or genuinely empty). Default:\n // no empty shell. Opt-in (`showWhenEmpty`): render the title + an empty-state\n // line so the section is always present (e.g. people-hub \"What I Shipped\").\n if (!visibleRefs.length) {\n if (!showWhenEmpty) return null; // non-showWhenEmpty consumers stay blank (unchanged)\n // Client-fetch loading (author/suggestion mode, no SSR initialItems): render a\n // SKELETON grid — reserves height + matches the rest of the app's loading, so\n // there's no blank-then-pop jump (the prior `return null` collapsed the tab to\n // zero height during the fetch). SSR controlled mode has isLoading=false → it\n // skips straight to the empty state below. Skeleton type = the requested rail\n // type (author mode passes a single `includeTypes`).\n if (isLoading) {\n const skeletonType = includeTypes?.[0] ?? entityType ?? 'blog_post_existing';\n return (\n <div className=\"space-y-8\">\n <h2 className=\"text-2xl font-bold text-ods-text-primary\">{title}</h2>\n <div className={gridClassFor(columns)}>\n {Array.from({ length: 3 }).map((_, i) => (\n <div key={i}>{renderSkeletonForType(skeletonType, 'default', adminCampaignCard)}</div>\n ))}\n </div>\n </div>\n );\n }\n return (\n <div className=\"space-y-8\">\n <h2 className=\"text-2xl font-bold text-ods-text-primary\">{title}</h2>\n {emptyState ?? <p className=\"text-ods-text-secondary\">{emptyStateText}</p>}\n </div>\n );\n }\n\n const grouped: Record<string, ContentRef[]> = {};\n for (const ref of visibleRefs) {\n if (!grouped[ref.type]) grouped[ref.type] = [];\n grouped[ref.type].push(ref);\n }\n\n // Registered types in CONTENT_REF_GROUPS order, then any unregistered\n // types appended (same shape the investor-email builder uses — both\n // consume `orderContentRefTypes` so cross-surface ordering matches).\n // SAME-TYPE FIRST: when a host entityType is known (suggestion / SSR\n // modes), its own content-type group is hoisted to the top — a blog\n // post's rail leads with blog posts. Rail group keys are compared via\n // the shared alias canonicalizer (blog_post_existing ↔ blog_post).\n let orderedTypes = orderContentRefTypes(Object.keys(grouped));\n if (entityType) {\n // Canonicalize BOTH sides — hosts pass registry vocab ('blog_post') but\n // rail-vocab aliases ('blog_post_existing') are also legal inputs; a raw\n // comparison would silently lose the same-type-first hoist for aliases.\n const canonicalEntityType = canonicalContentRefType(entityType);\n const sameType = orderedTypes.filter((t) => canonicalContentRefType(t) === canonicalEntityType);\n if (sameType.length > 0) {\n orderedTypes = [...sameType, ...orderedTypes.filter((t) => canonicalContentRefType(t) !== canonicalEntityType)];\n }\n }\n\n return (\n <div className=\"space-y-8\">\n <h2 className=\"text-2xl font-bold text-ods-text-primary\">{title}</h2>\n {orderedTypes.map((type) => (\n <ContentGroup\n key={type}\n type={type}\n refs={grouped[type]}\n columns={columns}\n buildUrl={effectiveBuildListUrl}\n resolveHref={resolveHref}\n LinkProvider={LinkProvider}\n extras={extras}\n adminCampaignCard={adminCampaignCard}\n heading={\n <h3 className=\"font-['Azeret_Mono'] text-[14px] font-semibold uppercase text-ods-text-secondary tracking-wider\">\n {getContentRefLabelOrTitleCase(type)}\n </h3>\n }\n />\n ))}\n </div>\n );\n}\n"]}
@@ -1,19 +1,19 @@
1
1
  "use client";
2
2
  import {
3
3
  OPENFRAME_DEV_SECTIONS,
4
- PageLayout,
5
4
  Pagination,
6
5
  StatusFilterComponent,
7
6
  TASK_TYPE_LABELS,
8
7
  TASK_TYPE_TEXT_COLORS,
9
8
  getStatusColorScheme,
10
9
  init_pagination
11
- } from "./chunk-OPSZNW3I.js";
10
+ } from "./chunk-VAIPAZPJ.js";
12
11
  import {
12
+ PageLayout,
13
13
  PageShell,
14
14
  SearchInput,
15
15
  StatusBadge
16
- } from "./chunk-BSOHVQ7X.js";
16
+ } from "./chunk-ZS4XNIZK.js";
17
17
  import {
18
18
  init_next_navigation,
19
19
  usePathname,
@@ -450,4 +450,4 @@ export {
450
450
  DevCardRowSkeletonList,
451
451
  DeliveryRow
452
452
  };
453
- //# sourceMappingURL=chunk-GL5X6VVC.js.map
453
+ //# sourceMappingURL=chunk-NITJSVUM.js.map
@@ -1,16 +1,22 @@
1
1
  "use client";
2
2
  import {
3
3
  useSelfFetch
4
- } from "./chunk-L4LAAA5Q.js";
4
+ } from "./chunk-IMNGGLKL.js";
5
5
  import {
6
6
  ChevronButton,
7
7
  buildSuggestionUrl,
8
8
  scrollElementIntoView,
9
9
  serializeJsonLd
10
- } from "./chunk-T2URJAX2.js";
10
+ } from "./chunk-VAIPAZPJ.js";
11
11
  import {
12
+ PageLayout,
13
+ PageShell,
12
14
  SECTION_HEADING_CLASS
13
- } from "./chunk-OO4ZGDRO.js";
15
+ } from "./chunk-ZS4XNIZK.js";
16
+ import {
17
+ init_next_navigation,
18
+ useRouter
19
+ } from "./chunk-PLJLE4A4.js";
14
20
  import {
15
21
  cn,
16
22
  init_cn
@@ -295,8 +301,38 @@ function FaqSection({
295
301
  ] });
296
302
  }
297
303
 
304
+ // src/components/faq/faq-document-page.tsx
305
+ init_next_navigation();
306
+ import { jsx as jsx3 } from "react/jsx-runtime";
307
+ function FaqDocumentPage({
308
+ title = "FAQs",
309
+ subtitle,
310
+ backButton,
311
+ apiBaseUrl,
312
+ entityType,
313
+ entityId,
314
+ minResults
315
+ }) {
316
+ const router = useRouter();
317
+ const backCfg = backButton === false ? void 0 : {
318
+ label: backButton?.label ?? "Back to home",
319
+ onClick: () => router.push(backButton?.href ?? "/")
320
+ };
321
+ return /* @__PURE__ */ jsx3(PageShell, { children: /* @__PURE__ */ jsx3(PageLayout, { title, subtitle, backButton: backCfg, children: /* @__PURE__ */ jsx3(
322
+ FaqSection,
323
+ {
324
+ heading: null,
325
+ apiBaseUrl,
326
+ entityType,
327
+ entityId,
328
+ minResults
329
+ }
330
+ ) }) });
331
+ }
332
+
298
333
  export {
299
334
  FaqAccordion,
300
- FaqSection
335
+ FaqSection,
336
+ FaqDocumentPage
301
337
  };
302
- //# sourceMappingURL=chunk-CYMMGPPV.js.map
338
+ //# sourceMappingURL=chunk-NPKMH446.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/components/faq/faq-section.tsx","../src/components/faq-accordion.tsx","../src/components/faq/json-ld.ts","../src/components/faq/faq-document-page.tsx"],"sourcesContent":["\"use client\"\n\nimport React, { useCallback, useEffect, useMemo, useState } from 'react'\nimport type { Faq } from '../../types/faq'\nimport { FaqAccordion, type FaqItem } from '../faq-accordion'\nimport { useSelfFetch } from '../../hooks/use-self-fetch'\nimport { buildSuggestionUrl } from '../../utils/suggestion-url'\nimport { serializeJsonLd } from '../../utils/common'\nimport { scrollElementIntoView } from '../../utils/scroll-into-view'\nimport { cn } from '../../utils/cn'\nimport { buildFaqJsonLdFromFaqs, type FaqSchemaOptions } from './json-ld'\nimport { SECTION_HEADING_CLASS } from '../layout/page-heading'\n\nexport interface FaqSectionProps {\n /**\n * SSR hydrate. When provided, the hook skips the first client fetch (per\n * useSelfFetch contract). The consuming server page resolves FAQs then drills\n * them into this prop — the lib never re-fetches what the host already gated on.\n */\n initialFaqs?: Faq[]\n /** Both required together for entity-attached FAQs; partial → bare /api/faqs. */\n entityType?: string\n entityId?: number | string\n /**\n * Heading node above the grouped list. `undefined` → default\n * `<h2>`\"Frequently Asked Questions\". `null` → no heading (the host page\n * owns the `<h1>`, as the standalone /faqs surface does). A React node lets a\n * platform drill its own <PageHeading> without the lib referencing platform\n * state. Also drives category nesting so the document outline stays correct:\n * `null` → categories render `<h2>` (directly under the page `<h1>`);\n * otherwise categories render `<h3>` beneath this heading.\n */\n heading?: React.ReactNode | null\n /** Inject FAQPage schema.org JSON-LD as a <script>. Off by default so embeds\n * don't emit duplicate schema. */\n emitJsonLd?: boolean\n /** Overrides for the JSON-LD's name/description/url. */\n jsonLd?: FaqSchemaOptions\n className?: string\n /** Maps to /api/faqs `?count=` (the 5-tier fill target). Absent → param\n * not sent (server default applies). */\n minResults?: number\n /** Fetch-URL prefix for third-party embeds / reverse proxies\n * ('' = same-origin relative). */\n apiBaseUrl?: string\n}\n\nconst DEFAULT_HEADING_TEXT = 'Frequently Asked Questions'\n\n/** URL composition shared with RelatedContentSection (`buildSuggestionUrl`)\n * — byte-identical to the historical `buildFaqsUrl` output when\n * `minResults`/`apiBaseUrl` are absent. */\nfunction buildFaqsUrl(\n entityType?: string,\n entityId?: number | string,\n minResults?: number,\n apiBaseUrl = '',\n): string {\n return buildSuggestionUrl('/api/faqs', { apiBaseUrl, entityType, entityId, count: minResults })\n}\n\n/** Stable, URL-safe anchor id for a category. Prefixed so it can't collide\n * with other in-page ids, and so a bare numeric/blank section still yields a\n * valid id. */\nfunction sectionSlug(section: string): string {\n return (\n 'faq-' +\n section\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n )\n}\n\ninterface FaqGroup {\n /** null → the uncategorized bucket: no heading, no jump pill, rendered last. */\n section: string | null\n slug: string | null\n items: FaqItem[]\n}\n\n/** Group FAQs by `faq.section`, preserving the server's first-seen\n * (display_order) order for BOTH the section order and the rows within each\n * section. The uncategorized bucket (blank/missing section) sinks to the end\n * since it renders without a heading. Items carry NO badge here — the `<h2>`\n * IS the category, so a per-row chip would be redundant. */\nfunction groupFaqsBySection(faqs: Faq[]): FaqGroup[] {\n const order: string[] = []\n const byName = new Map<string, FaqGroup>()\n let uncategorized: FaqGroup | null = null\n for (const faq of faqs) {\n const item: FaqItem = { id: faq.id, question: faq.question, answer: faq.answer }\n const name = faq.section?.trim()\n if (!name) {\n if (!uncategorized) uncategorized = { section: null, slug: null, items: [] }\n uncategorized.items.push(item)\n continue\n }\n let group = byName.get(name)\n if (!group) {\n group = { section: name, slug: sectionSlug(name), items: [] }\n byName.set(name, group)\n order.push(name)\n }\n group.items.push(item)\n }\n const groups = order.map((name) => byName.get(name)!)\n if (uncategorized) groups.push(uncategorized)\n return groups\n}\n\n/** The standard hub sticky-header height — same offset `useNavLink`'s hash\n * scroll uses, so a category jump lands below the header, not under it. */\nconst FAQ_NAV_HEADER_OFFSET = 96\n\n/**\n * Grouped FAQ layout: a category jump-nav above stacked `<h2>` category\n * sections (each its own accordion). Isolated into its own component so the\n * scroll-spy hooks only mount in grouped mode — `FaqSection`'s own hooks stay\n * unconditional.\n *\n * The pills are real `<a href=\"#slug\">` anchors (crawlable in-page links,\n * deep-linkable, work without JS); the click handler upgrades the jump to the\n * cancellation-proof `scrollElementIntoView` tween and syncs the URL hash.\n */\nfunction GroupedFaqList({\n groups,\n categoryHeadingAs,\n}: {\n groups: FaqGroup[]\n /** Heading tag for each category, so the document outline nests correctly\n * under whatever owns the heading above this block: `h2` on the standalone\n * page (the page owns the `<h1>`), `h3` beneath an embed's `<h2>` title.\n * The VISUAL is `SECTION_HEADING_CLASS` either way, so categories look\n * identical on every surface. */\n categoryHeadingAs: 'h2' | 'h3'\n}) {\n const CategoryHeading = categoryHeadingAs\n const navGroups = useMemo(() => groups.filter((g) => g.slug), [groups])\n const [activeSlug, setActiveSlug] = useState<string | null>(navGroups[0]?.slug ?? null)\n // Identity-stable key for the section set so the observer re-binds only when\n // the categories actually change (not on every parent re-render).\n const slugKey = navGroups.map((g) => g.slug).join('|')\n\n // Scroll-spy: mark the pill for the category currently at the top of the\n // viewport. rootMargin drops the trigger line just below the sticky header\n // and ignores the bottom ~55% so \"active\" is the section being read, not the\n // next one peeking in.\n useEffect(() => {\n if (navGroups.length < 2) return\n const tops = new Map<string, number>()\n const observer = new IntersectionObserver(\n (entries) => {\n for (const entry of entries) {\n const id = (entry.target as HTMLElement).id\n if (entry.isIntersecting) tops.set(id, entry.boundingClientRect.top)\n else tops.delete(id)\n }\n let bestId: string | null = null\n let bestTop = Number.POSITIVE_INFINITY\n for (const [id, top] of tops) {\n if (top < bestTop) {\n bestTop = top\n bestId = id\n }\n }\n if (bestId) setActiveSlug(bestId)\n },\n { rootMargin: `-${FAQ_NAV_HEADER_OFFSET}px 0px -55% 0px`, threshold: 0 },\n )\n for (const group of navGroups) {\n const el = group.slug ? document.getElementById(group.slug) : null\n if (el) observer.observe(el)\n }\n return () => observer.disconnect()\n // slugKey encodes the section set; re-observe only when it changes.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [slugKey])\n\n const handleJump = useCallback(\n (e: React.MouseEvent<HTMLAnchorElement>, slug: string) => {\n e.preventDefault()\n setActiveSlug(slug)\n scrollElementIntoView(document.getElementById(slug), {\n headerOffset: FAQ_NAV_HEADER_OFFSET,\n })\n if (typeof history !== 'undefined') history.replaceState(null, '', `#${slug}`)\n },\n [],\n )\n\n return (\n <div className=\"space-y-8\">\n {navGroups.length > 1 && (\n <nav aria-label=\"FAQ categories\" className=\"flex flex-wrap gap-2\">\n {navGroups.map((group) => {\n const isActive = group.slug === activeSlug\n return (\n <a\n key={group.slug}\n href={`#${group.slug}`}\n aria-current={isActive ? 'true' : undefined}\n onClick={(e) => handleJump(e, group.slug as string)}\n className={cn(\n \"rounded-full border px-4 py-2 text-sm font-medium font-['DM_Sans'] transition-colors\",\n isActive\n ? 'border-ods-text-primary bg-ods-card text-ods-text-primary'\n : 'border-ods-border bg-ods-card text-ods-text-secondary hover:border-ods-text-secondary hover:text-ods-text-primary',\n )}\n >\n {group.section}\n </a>\n )\n })}\n </nav>\n )}\n <div className=\"space-y-10\">\n {groups.map((group) => (\n <section\n key={group.slug ?? 'faq-uncategorized'}\n id={group.slug ?? undefined}\n className=\"scroll-mt-24 space-y-4\"\n >\n {group.section && (\n <CategoryHeading className={SECTION_HEADING_CLASS}>{group.section}</CategoryHeading>\n )}\n <FaqAccordion items={group.items} />\n </section>\n ))}\n </div>\n </div>\n )\n}\n\nfunction FaqSkeleton() {\n return (\n <div className=\"space-y-8 animate-pulse\">\n <div className=\"h-12 md:h-14 w-2/3 rounded bg-ods-border\" />\n <div className=\"rounded-3xl border border-ods-border overflow-hidden bg-ods-card divide-y divide-ods-border w-full\">\n {Array.from({ length: 8 }).map((_, idx) => (\n <div key={idx} className=\"flex items-center justify-between px-6 md:px-8 py-6\">\n <div className=\"h-6 w-5/6 rounded bg-ods-border\" />\n <div className=\"h-10 w-10 rounded-md bg-ods-border\" />\n </div>\n ))}\n </div>\n </div>\n )\n}\n\ninterface FaqsResponse {\n faqs: Faq[]\n}\n\n/**\n * The FAQ display surface — ONE rendering on every host: the list grouped by\n * `faq.section` (each category a heading + its own accordion, with a category\n * jump-nav once there are 2+ categories). There is no flat/ungrouped mode and\n * no page-vs-embedded shell fork; the standalone /faqs page and every embed\n * render through this single path, so they cannot drift.\n *\n * - Standalone /faqs page: pass `initialFaqs` (SSR) + `heading={null}` (the\n * page owns the <h1>) + `emitJsonLd` with `jsonLd` overrides for SEO.\n * - Per-entity embed: pass `entityType` + `entityId` (no `initialFaqs`); the\n * hook self-fetches `GET /api/faqs`, and `heading` is this block's own <h2>.\n *\n * CONTRACT: the consuming app MUST implement `GET /api/faqs`. On a fetch error\n * (or zero FAQs) the component renders nothing so the host page isn't\n * disfigured. The host always supplies the page shell — this renders a bare\n * <section>.\n */\nexport function FaqSection({\n initialFaqs,\n entityType,\n entityId,\n heading,\n emitJsonLd = false,\n jsonLd,\n className,\n minResults,\n apiBaseUrl = '',\n}: FaqSectionProps) {\n const url = buildFaqsUrl(entityType, entityId, minResults, apiBaseUrl)\n // Memoized — useSelfFetch re-syncs on [initialData]; a fresh per-render\n // wrapper object would setState-loop under re-rendering parents.\n const initialData = useMemo<FaqsResponse | undefined>(\n () => (initialFaqs ? { faqs: initialFaqs } : undefined),\n [initialFaqs],\n )\n const { data, isLoading, error } = useSelfFetch<FaqsResponse>(url, { initialData })\n\n const faqs = data?.faqs ?? []\n // Grouped before the early returns so the hook order stays stable.\n const groups = useMemo(() => (faqs.length > 0 ? groupFaqsBySection(faqs) : []), [faqs])\n\n // `undefined` → default <h2> title; `null` → the host page owns the <h1>, so\n // no title renders here. `heading === null` also makes the category headings\n // <h2> (directly under the page <h1>); otherwise they nest as <h3>.\n const headingNode =\n heading === undefined ? <h2 className={SECTION_HEADING_CLASS}>{DEFAULT_HEADING_TEXT}</h2> : heading\n\n // Degrade silently — never show an error banner or an empty section shell\n // where FAQs would be (host pages and the standalone surface both rely on it).\n if (error) return null\n if (!isLoading && faqs.length === 0) return null\n if (isLoading && faqs.length === 0) {\n return (\n <div className={className}>\n <FaqSkeleton />\n </div>\n )\n }\n\n const schema = emitJsonLd ? buildFaqJsonLdFromFaqs(faqs, jsonLd) : null\n\n return (\n <>\n <section className={className ?? 'space-y-10'}>\n {headingNode}\n <GroupedFaqList groups={groups} categoryHeadingAs={heading === null ? 'h2' : 'h3'} />\n </section>\n {schema && (\n <script\n type=\"application/ld+json\"\n // eslint-disable-next-line react/no-danger\n // serializeJsonLd, NOT raw JSON.stringify — FAQ answers are\n // admin-entered; an embedded \"</script>\" must not break the tag.\n dangerouslySetInnerHTML={{ __html: serializeJsonLd(schema) }}\n />\n )}\n </>\n )\n}\n","\"use client\"\n\nimport React, { useRef, useState, useEffect, useCallback } from 'react'\nimport { ChevronButton } from './ui/chevron-button'\nimport { cn } from \"../utils/cn\"\n\nexport interface FaqItem {\n id: number | string\n question: string\n answer: string\n}\n\ninterface FaqAccordionProps {\n items: FaqItem[]\n defaultOpenIds?: (number | string)[]\n}\n\n// Utility to measure scrollHeight outside render cycle\nconst useMeasuredHeight = (isOpen: boolean) => {\n const ref = useRef<HTMLDivElement | null>(null)\n const [maxHeight, setMaxHeight] = useState<string>('0px')\n\n const measure = useCallback(() => {\n if (ref.current) {\n const height = ref.current.scrollHeight\n setMaxHeight(`${height}px`)\n }\n }, [])\n\n // Update height only when section is open\n useEffect(() => {\n if (isOpen) {\n measure()\n } else {\n setMaxHeight('0px')\n }\n }, [isOpen, measure])\n\n return { ref, maxHeight }\n}\n\nexport function FaqAccordion({ items, defaultOpenIds = [] }: FaqAccordionProps) {\n const [openSet, setOpenSet] = useState<Set<string | number>>(new Set(defaultOpenIds))\n\n const toggle = (id: string | number) => {\n setOpenSet(prev => {\n const next = new Set(prev)\n if (next.has(id)) next.delete(id)\n else next.add(id)\n return next\n })\n }\n\n return (\n <div className=\"rounded-3xl border border-ods-border divide-y divide-ods-border bg-ods-card overflow-hidden\">\n {items.map(item => {\n const isOpen = openSet.has(item.id)\n const { ref, maxHeight } = useMeasuredHeight(isOpen)\n\n return (\n <div\n key={item.id}\n className={cn('group transition-colors hover:bg-[#1E1E1E]', isOpen ? 'bg-ods-bg' : 'bg-transparent')}\n >\n {/* Header */}\n <div\n role=\"button\"\n tabIndex={0}\n onClick={() => toggle(item.id)}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n toggle(item.id);\n }\n }}\n aria-expanded={isOpen}\n className=\"flex w-full items-center justify-between px-6 md:px-8 py-6 text-left focus:outline-none transition-colors cursor-pointer\"\n >\n <div className=\"min-w-0 pr-4\">\n <h3>\n {item.question}\n </h3>\n </div>\n <div className=\"flex-shrink-0\">\n <ChevronButton\n aria-label={isOpen ? 'Collapse question' : 'Expand question'}\n size=\"md\"\n isExpanded={isOpen}\n backgroundColor=\"transparent\"\n borderColor=\"#3A3A3A\"\n />\n </div>\n </div>\n {/* Content wrapper with max-height animation */}\n <div\n style={{ maxHeight, transition: 'max-height 0.35s ease-in-out, opacity 0.35s ease-in-out', opacity: isOpen ? 1 : 0 }}\n className=\"overflow-hidden group-hover:bg-[#1E1E1E]/30\"\n >\n <div ref={ref} className=\"px-6 md:px-8 pb-6 text-ods-text-primary text-h4\">\n {item.answer}\n </div>\n </div>\n </div>\n )\n })}\n </div>\n )\n} ","/**\n * Pure FAQ JSON-LD builder. No React, no client-only deps — safe to import from\n * Server Components via the `./components/faq` subpath export. (Do NOT import\n * through the root `./components` barrel — that barrel is `\"use client\"` and\n * dragging it into a Server Component would force the client graph into the\n * server.)\n *\n * The hub used to harcode `name`/`description` here for OpenMSP; in the lib we\n * accept overrides so every embedder can supply its own platform branding.\n */\nimport type { Faq } from '../../types/faq'\n\nexport interface FaqSchemaOptions {\n name?: string\n description?: string\n url?: string\n}\n\nconst DEFAULT_NAME = 'Frequently Asked Questions'\nconst DEFAULT_DESCRIPTION =\n 'Answers to common questions.'\n\nexport function baseFaqSchema(opts: FaqSchemaOptions = {}) {\n return {\n '@context': 'https://schema.org',\n '@type': 'FAQPage',\n name: opts.name ?? DEFAULT_NAME,\n description: opts.description ?? DEFAULT_DESCRIPTION,\n ...(opts.url ? { url: opts.url } : {}),\n } as const\n}\n\nexport function buildFaqJsonLdFromFaqs(faqs: Faq[], opts: FaqSchemaOptions = {}) {\n return {\n ...baseFaqSchema(opts),\n mainEntity: faqs.map((faq) => ({\n '@type': 'Question',\n name: faq.question,\n acceptedAnswer: {\n '@type': 'Answer',\n text: faq.answer,\n },\n })),\n } as const\n}\n","'use client';\n\n/**\n * FaqDocumentPage — the full `/faqs` page with chrome, so embedders drop in\n * ONE component instead of hand-assembling `PageShell` + `PageLayout` around the\n * bare `<FaqSection>`. Mirrors `LegalDocumentPage` / `DevSectionPage`: the\n * page-level layout lives in the lib, the host passes only config + a back button.\n *\n * `<FaqSection heading={null}>` lets `PageLayout`'s `TitleBlock` own the heading +\n * back button; it self-fetches `${apiBaseUrl}/api/faqs` via the authed\n * `useSelfFetch`, and renders nothing on a fetch error or zero FAQs.\n */\n\nimport { PageShell, PageLayout } from '../ui';\nimport { useRouter } from '../../embed-shims/next-navigation';\nimport { FaqSection } from './faq-section';\n\nexport interface FaqDocumentPageProps {\n /** Page title (PageLayout TitleBlock). Default \"FAQs\". */\n title?: string;\n /** Subtitle under the title. */\n subtitle?: string;\n /** Back-button config — same pattern as `DevSectionPage` / `LegalDocumentPage`.\n * Pass `false` to hide. Default `{ label: 'Back to home', href: '/' }`. */\n backButton?: { label?: string; href?: string } | false;\n /** Base URL `FaqSection` appends `/api/faqs` to (reverse-proxy embedders). */\n apiBaseUrl?: string;\n /** Optional entity scoping forwarded to `FaqSection`. */\n entityType?: string;\n entityId?: number | string;\n /** Minimum FAQ count before the section renders (forwarded to `FaqSection`). */\n minResults?: number;\n}\n\nexport function FaqDocumentPage({\n title = 'FAQs',\n subtitle,\n backButton,\n apiBaseUrl,\n entityType,\n entityId,\n minResults,\n}: FaqDocumentPageProps) {\n const router = useRouter();\n\n // Back-button config — mirrors LegalDocumentPage/DevSectionPage. Hide entirely\n // when the caller passes `false` (embed-mode where the host owns nav chrome).\n const backCfg =\n backButton === false\n ? undefined\n : {\n label: backButton?.label ?? 'Back to home',\n onClick: () => router.push(backButton?.href ?? '/'),\n };\n\n return (\n <PageShell>\n <PageLayout title={title} subtitle={subtitle} backButton={backCfg}>\n <FaqSection\n heading={null}\n apiBaseUrl={apiBaseUrl}\n entityType={entityType}\n entityId={entityId}\n minResults={minResults}\n />\n </PageLayout>\n </PageShell>\n );\n}\n"],"mappings":"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","names":["useCallback","useEffect","useState","jsx","jsxs","useState","useEffect","useCallback","jsx"]}
@@ -21,4 +21,4 @@ function useRequiredChatRuntime() {
21
21
 
22
22
 
23
23
  exports.ChatRuntimeContext = ChatRuntimeContext; exports.useChatRuntime = useChatRuntime; exports.useRequiredChatRuntime = useRequiredChatRuntime;
24
- //# sourceMappingURL=chunk-MEAR2WHS.cjs.map
24
+ //# sourceMappingURL=chunk-PC7YOZ4I.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-PC7YOZ4I.cjs","../src/contexts/chat-runtime-context.tsx"],"names":[],"mappings":"AAAA,qFAAY;AACZ;AACA;ACiCA,8BAA0D;AAqKnD,IAAM,mBAAA,EAAqB,kCAAA,IAAsC,CAAA;AAQjE,SAAS,cAAA,CAAA,EAAqC;AACnD,EAAA,OAAO,+BAAA,kBAA6B,CAAA;AACtC;AAUO,SAAS,sBAAA,CAAA,EAAsC;AACpD,EAAA,MAAM,EAAA,EAAI,+BAAA,kBAA6B,CAAA;AACvC,EAAA,GAAA,CAAI,CAAC,CAAA,EAAG;AACN,IAAA,MAAM,IAAI,KAAA;AAAA,MACR;AAAA,IAMF,CAAA;AAAA,EACF;AACA,EAAA,OAAO,CAAA;AACT;ADxNA;AACA;AACE;AACA;AACA;AACF,kJAAC","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-PC7YOZ4I.cjs","sourcesContent":[null,"'use client'\n\n/**\n * Chat runtime context — single seam for embedding the chat panel in a\n * different host (e.g. user1.openframe.ai reverse-proxying API calls\n * under /api/mingo-guide/* to hub.openframe.ai/api/*).\n *\n * Three concerns, one context:\n * 1. API endpoints: chatStreamUrl / approvalToolUrl / commandsUrl /\n * buildListUrl + attachment endpoints + chat-identity. The chat\n * reads them from runtime; hub vs embedded app supply different\n * strings via different providers.\n * 2. Navigation mode + callbacks: 'host' or 'embed' mode. Host wires\n * its own router/docNav via the optional `navigate` callback\n * (plain function, NOT a hook); embed forces new-tab via\n * `defaultContentOrigin` + lib's `resolveExternalNavigation`.\n * 3. Identity context: only `source` (required for localStorage\n * namespacing). The display identity (greeting first-name etc.)\n * comes from the server via `useChatIdentity()` — never injected\n * client-side, so it always matches the server-resolved auth.\n *\n * Sibling of EndpointsRuntimeContext (announcement bar, contact form,\n * access codes). Each runtime stays an independent React context so\n * embedders can opt into either feature without forcing the other.\n *\n * IMPORTANT for embedders: memoize the value passed to\n * `<ChatRuntimeContext.Provider value={...}>` (e.g. via React.useMemo).\n * Every change to its reference identity invalidates downstream\n * `useMemo` consumers (the chat input's slash-commands binding,\n * useNavLink's embed-resolution memo, useDocChat's streamFn factory).\n * The hub's `<HubRuntimeProvider>` already memoizes correctly with\n * stable deps. Embedded apps that build the value inline on each render\n * will pay an avoidable re-render cost across the entire chat tree.\n */\n\nimport { createContext, useContext, type ReactNode } from 'react'\n\nimport type { ComposeContentUrl } from '../utils/content-href'\n\n/**\n * Runtime config consumed by the chat panel.\n */\nexport interface ChatRuntime {\n endpoints: {\n /** POST streaming chat. Hub: '/api/docs/chat'. */\n chatStreamUrl: string\n /** POST agent approve/reject. Hub: '/api/chat/agent/confirm-tool'. */\n approvalToolUrl: string\n /** Customer-ticket agent endpoints (Help Center). OPTIONAL — when unset,\n * the ticket hooks fall back to the bare hub paths\n * (`/api/chat/agent/{find-ticket,ticket-action,list-engagements}`).\n * Embedders behind a reverse proxy set these to their proxied paths\n * (e.g. `/content/api/chat/agent/...`) so tickets route through the SAME\n * endpoint config + proxy as every other endpoint. */\n findTicketUrl?: string\n ticketActionUrl?: string\n listEngagementsUrl?: string\n /** GET slash-command catalog. Hub: '/api/docs/commands'. */\n commandsUrl: string\n /** Build entity-card list URL for a content type + ids. Hub delegates\n * to the rag-table-config registry; embedded app provides its own\n * per-type URL builder against the reverse proxy. Returns null when\n * the type has no list endpoint (caller skips rendering). */\n buildListUrl: (type: string, ids: string[]) => string | null\n /** Chat-attachment endpoints — added for the v2 attachment feature.\n *\n * Three concerns:\n * - `attachmentUploadUrl` — POSTed by the chat-attachment hook\n * to mint a Supabase signed-upload-URL + HMAC view token.\n * - `attachmentViewUrlPrefix` — embedded in markdown URLs the\n * chat hosts in user message bubbles (`![]()` / `[Attached]`).\n * Stored in chat history; chosen at SEND time. In host mode the\n * relative `/api/storage/view/chat-attachments/` is sufficient\n * (same-origin); embedders supply an absolute hub URL so the\n * browser can fetch cross-origin.\n * - `identityUrl` — GET endpoint the `useChatIdentity` hook\n * hits to learn the `{authTier, source, attachmentsEnabled}`\n * capability bag for the current session. Used beyond chat\n * (tickets / contact form / any embedded surface that needs\n * to identify the proxied customer), so the name has no\n * \"chat\" prefix even though the consuming hook still does. */\n attachmentUploadUrl: string\n attachmentViewUrlPrefix: string\n identityUrl: string\n /** Optional URL prefix for the image proxy (`<prefix>?url=<external>`).\n * When unset, lib's `getProxiedImageUrl` returns the original URL\n * unchanged. Hub default: '/api/image-proxy'. Embedders that don't\n * host an image-proxy route leave this undefined → images load\n * directly cross-origin (CORS-permitting). */\n imageProxyUrlPrefix?: string\n /** Optional list of hostnames that should bypass the image proxy\n * (rendered direct). Hub uses ['openmsp.ai']; embedders typically\n * leave it unset. Matches the `skipDomains` parameter of\n * `getProxiedImageUrl`. */\n imageProxySkipDomains?: string[]\n /** Supabase storage origin (e.g. `https://xyz.supabase.co`) — used\n * by `useVideoWarmup` to scope the `<link rel=\"preload\" as=\"video\">`\n * hint to MP4s the deployment actually hosts. Hub wires it via\n * `getSupabaseStorageOrigin()`; embedders without a Supabase\n * storage origin leave it unset (preload is then skipped; Mux/\n * YouTube preconnect still fires). */\n supabaseStorageOrigin?: string\n }\n navigation: {\n /** ONE knob, two behaviors:\n * - 'host' = use the host page's existing click-routing untouched.\n * The chat panel calls `navigate?.()` for in-app routing.\n * - 'embed' = guest inside another app: short-circuit at the top\n * of click handlers to force new-tab + absolutize via\n * resolveExternalNavigation. */\n mode: 'host' | 'embed'\n /** Embed-only fallback origin for relative URLs whose target platform\n * can't be inferred. Used by resolveExternalNavigation when\n * `targetPlatform` is null — without this, a relative `/foo` href would\n * window.open against the embedder's origin, which is WRONG.\n * Set to your content host (e.g. 'https://hub.openframe.ai').\n * Required by the embedded app whenever mode='embed'. */\n defaultContentOrigin?: string\n /** Override for opening external URLs. MUST BE SYNCHRONOUS —\n * Safari/Firefox block popups opened outside a direct user gesture.\n * Default: window.open(href, '_blank', 'noopener,noreferrer'). */\n openExternal?: (href: string) => void\n /** Optional in-app navigation callback (host-mode only).\n * Returns `true` if the host handled the click in-app\n * (router.push + docNav.navigate); returns `false`, `undefined`,\n * or `void` → lib falls back to window.location.assign(href).\n * Hub wires this via HubRuntimeProvider's HubNavigationWiring;\n * embedders not in Next.js leave it undefined. */\n navigate?: (input: { href: string; path?: string | null; targetPlatform?: string | null }) => boolean | void\n /** Optional new-tab decision callback. Returns true → lib opens in\n * new tab; false → same tab via `navigate`. Hub wires the existing\n * `decideNewTab` logic from use-nav-link.tsx (re-imports the pure\n * helper from lib). Embedders may omit; lib defaults to:\n * same-origin/same-platform → same tab, else new tab. */\n decideNewTab?: (args: { href: string; targetPlatform?: string | null }) => boolean\n }\n /** Optional OG placeholder URL builder. Returns a branded\n * `/api/og-placeholder?...` URL for the given title. Hub wires this\n * to its `buildOgPlaceholderUrl` (resolves CSS-var ODS colors to\n * hex via the static map). Embedders can wire any equivalent that\n * hits their own placeholder route — or omit, in which case entity\n * cards fall back to no placeholder.\n *\n * Pure synchronous function — NOT a hook. Callers wrap with\n * `useMemo`/`useOgPlaceholder` for memoization. */\n resolvePlaceholderUrl?: (\n title: string,\n options?: { site?: string; aspect?: 'wide' | 'square' },\n ) => string\n /** Optional content-URL composer. Returns the platform-aware href +\n * target-platform tuple for a content entity. Hub wires this to its\n * `buildContentURL(type, slug, extractPrimaryPlatform(platforms))`\n * pipeline so the lib catalog/detail views can derive cross-\n * platform hrefs without knowing the hub's platform topology\n * (openmsp.ai / openframe.app / flamingo.run / tmcg).\n *\n * THE single content-href authority for every embeddable surface — page\n * views (onboarding catalog/detail, releases) AND chat cards / chips /\n * search results all resolve content links through this one seam, so a\n * given type lands in the SAME place regardless of where it's rendered.\n * Embedders wire `makeComposeContentUrl({ hostedTypes, contentOrigin })`;\n * omit it and lib views fall back to a same-origin relative path\n * (`buildDefaultHref`).\n *\n * Takes a single `ComposeContentUrlInput`: `type` + `identifier` (page\n * views pass the slug; chat rows pass the id + `externalUrl`, whose path\n * yields the slug for in-app routing) + optional `platforms` /\n * `externalUrl` / `targetPlatform`. */\n composeContentUrl?: ComposeContentUrl\n /** Per-`documentType` doc-viewer targets — the UNIFIED, DYNAMIC replacement for\n * the single `chipBasePlatform` prop. Maps a doc-table documentType\n * (`'markdown'`, `'data_room_doc'`, …) → `{ platform, basePath }` for the PUBLIC\n * doc viewer that hosts it. Doc chips with no `externalUrl` resolve PER ROW to\n * `getBaseUrl(platform)/<basePath>/<path>`, so a chat mixing several doc sources\n * sends EACH to its own home (markdown→flamingo/knowledge-base,\n * data_room_doc→company-hub/data-room) instead of one static fallback. The hub\n * may keep using `chipBasePlatform` (one doc source per platform); embedders that\n * surface multiple doc sources wire this. Threaded into `resolveSourceRowCTA`. */\n docPlatformTargets?: Record<string, { platform: string; basePath: string }>\n /** Chat source / platform identifier — OPTIONAL. The hub sets it from\n * `currentPlatform()`; EMBEDDERS leave it unset and stay platform-agnostic.\n *\n * It is NOT required for chat to work. The wire resolves source server-side\n * (`/docs/chat|search|commands` reject any client `source`); the\n * same-tab-vs-new-tab link decision falls back to an origin comparison when\n * it's absent (`decideNewTab` → `isCrossOriginUrl`); and the localStorage\n * history namespace falls back to a stable constant. Set it only where the\n * client legitimately needs to know its platform a priori — i.e. the hub,\n * where several platforms share related origins so \"same platform\" can't be\n * inferred from a URL alone. */\n source?: string\n // NOTE: No `user` field. The chat's display identity (greeting\n // first-name, etc.) comes from the SERVER-resolved auth via\n // `useChatIdentity()` — the same identity the server uses to\n // authorize requests. Letting embedders pass a client-side `user`\n // would let it desync from the actual auth tier, causing greetings\n // like \"Hey Bob\" while the server treats the session as\n // alice@example.com. Single source of truth: the server.\n}\n\nexport const ChatRuntimeContext = createContext<ChatRuntime | null>(null)\n\n/**\n * Returns the active runtime, or null when no provider is mounted.\n * NULL is a first-class value — it signals \"no chat runtime configured.\"\n * Optional consumers fall back to no-op behavior; strict consumers\n * use `useRequiredChatRuntime` (below).\n */\nexport function useChatRuntime(): ChatRuntime | null {\n return useContext(ChatRuntimeContext)\n}\n\n/**\n * Strict variant used INSIDE the chat panel. Throws if no provider.\n * The hub guarantees one exists by mounting `<HubRuntimeProvider>` at\n * root; the embedded app mounts its own `<ChatRuntimeContext.Provider>`\n * at the tree root. In Jest / Storybook tests that render chat\n * internals directly, wrap with `<HubRuntimeProvider>` (hub defaults)\n * or supply `<ChatRuntimeContext.Provider value={mockedRuntime}>`.\n */\nexport function useRequiredChatRuntime(): ChatRuntime {\n const v = useContext(ChatRuntimeContext)\n if (!v) {\n throw new Error(\n '[chat-runtime] hook called outside a <ChatRuntimeContext.Provider>. ' +\n 'The hub mounts <HubRuntimeProvider> at root — this only fires when ' +\n 'chat internals are rendered above the provider tree. ' +\n 'Fix: ensure the rendering subtree descends from the runtime provider. ' +\n 'In tests/Storybook: wrap with <HubRuntimeProvider> or supply ' +\n 'a <ChatRuntimeContext.Provider value={mockedRuntime}>.',\n )\n }\n return v\n}\n"]}
@@ -7,7 +7,8 @@ var _chunk27APPAJNcjs = require('./chunk-27APPAJN.cjs');
7
7
  var _chunkPXXS27EEcjs = require('./chunk-PXXS27EE.cjs');
8
8
 
9
9
 
10
- var _chunkDAAFVOTLcjs = require('./chunk-DAAFVOTL.cjs');
10
+
11
+ var _chunkZSUNQJROcjs = require('./chunk-ZSUNQJRO.cjs');
11
12
 
12
13
 
13
14
 
@@ -667,7 +668,7 @@ function useSearch(config) {
667
668
  const [results, setResults] = _react.useState.call(void 0, []);
668
669
  const [isLoading, setIsLoading] = _react.useState.call(void 0, false);
669
670
  const [error, setError] = _react.useState.call(void 0, null);
670
- const debouncedQuery = _chunkDAAFVOTLcjs.useDebounce.call(void 0, query, debounceMs);
671
+ const debouncedQuery = _chunkZSUNQJROcjs.useDebounce.call(void 0, query, debounceMs);
671
672
  const clearResults = _react.useCallback.call(void 0, () => {
672
673
  setResults([]);
673
674
  setError(null);
@@ -1436,7 +1437,7 @@ function useContactSubmission(options = {}) {
1436
1437
  if (isSubmitting) return;
1437
1438
  setIsSubmitting(true);
1438
1439
  try {
1439
- const response = await fetch(contactUrl, {
1440
+ const response = await _chunkZSUNQJROcjs.contentFetch.call(void 0, contactUrl, {
1440
1441
  method: "POST",
1441
1442
  headers: { "Content-Type": "application/json" },
1442
1443
  body: JSON.stringify({
@@ -3039,4 +3040,4 @@ function useHumanitySignals() {
3039
3040
 
3040
3041
 
3041
3042
  exports.useHeaderHeight = useHeaderHeight; exports.useHorizontalScrollbar = useHorizontalScrollbar; exports.useImageEdgeColor = useImageEdgeColor; exports.useLocalStorage = useLocalStorage; exports.useMediaQuery = useMediaQuery; exports.breakpoints = breakpoints; exports.useSmUp = useSmUp; exports.useMdUp = useMdUp; exports.useLgUp = useLgUp; exports.useMemoizedCallback = useMemoizedCallback; exports.useNotificationPermission = useNotificationPermission; exports.useOnboardingState = useOnboardingState; exports.useSearch = useSearch; exports.useTablePagination = useTablePagination; exports.useThrottle = useThrottle; exports.useWindowSize = useWindowSize; exports.platformIcons = platformIcons; exports.platformColors = platformColors; exports.platformDisplayNames = platformDisplayNames; exports.platformDescriptions = platformDescriptions; exports.platformSlogans = platformSlogans; exports.platformHexColors = platformHexColors; exports.platformIconNames = platformIconNames; exports.getDefaultColorForPlatform = getDefaultColorForPlatform; exports.getDefaultIconForPlatform = getDefaultIconForPlatform; exports.transformPlatformConfigsToOptions = transformPlatformConfigsToOptions; exports.getPlatformIcon = getPlatformIcon; exports.getPlatformColor = getPlatformColor; exports.getPlatformDisplayName = getPlatformDisplayName; exports.getPlatformDescription = getPlatformDescription; exports.getPlatformSlogan = getPlatformSlogan; exports.getSmallPlatformIcon = getSmallPlatformIcon; exports.getPlatformIconComponent = getPlatformIconComponent; exports.usePlatformConfig = usePlatformConfig; exports.usePlatformByValue = usePlatformByValue; exports.useValidatePlatform = useValidatePlatform; exports.ToolTypeValues = ToolTypeValues; exports.toolLabels = toolLabels; exports.ToolIcon = ToolIcon; exports.dotColorByVariant = dotColorByVariant; exports.progressColorByVariant = progressColorByVariant; exports.ToastCard = ToastCard; exports.CommandApprovalToast = CommandApprovalToast; exports.Toaster = Toaster; exports.showToast = showToast; exports.showCommandApprovalToast = showCommandApprovalToast; exports.toast = toast; exports.useToast = useToast; exports.useContactSubmission = useContactSubmission; exports.useQuickActionHint = useQuickActionHint; exports.useCopyToClipboard = useCopyToClipboard; exports.configureBatchImageFetch = configureBatchImageFetch; exports.batchFetchAuthenticatedImages = batchFetchAuthenticatedImages; exports.useBatchImages = useBatchImages; exports.configureAuthenticatedImage = configureAuthenticatedImage; exports.useAuthenticatedImage = useAuthenticatedImage; exports.extractVariablesFromQuery = extractVariablesFromQuery; exports.isScalarType = isScalarType; exports.isInputObjectType = isInputObjectType; exports.flattenQueryVariables = flattenQueryVariables; exports.mergeDefaults = mergeDefaults; exports.validateSchema = validateSchema; exports.getArrayParams = getArrayParams; exports.getRequiredParams = getRequiredParams; exports.shouldIncludeInUrl = shouldIncludeInUrl; exports.urlParamsToVariables = urlParamsToVariables; exports.variablesToUrlParams = variablesToUrlParams; exports.coerceValue = coerceValue; exports.setNestedValue = setNestedValue; exports.getNestedValue = getNestedValue; exports.mergeVariables = mergeVariables; exports.clearParams = clearParams; exports.validateVariables = validateVariables; exports.GraphQLIntrospector = GraphQLIntrospector; exports.introspector = introspector; exports.useQueryParams = useQueryParams; exports.useApiParams = useApiParams; exports.createSearchParams = createSearchParams; exports.useCursorPaginationState = useCursorPaginationState; exports.useNatsClient = useNatsClient; exports.validateAccessCode = validateAccessCode; exports.consumeAccessCode = consumeAccessCode; exports.validateAndConsumeAccessCode = validateAndConsumeAccessCode; exports.useAccessCodeIntegration = useAccessCodeIntegration; exports.HONEYPOT_FIELD = HONEYPOT_FIELD; exports.ELAPSED_MS_FIELD = ELAPSED_MS_FIELD; exports.DEFAULT_MIN_FILL_MS = DEFAULT_MIN_FILL_MS; exports.extractHumanitySignals = extractHumanitySignals; exports.evaluateHumanitySignals = evaluateHumanitySignals; exports.splitCsvEnv = splitCsvEnv; exports.useHumanitySignals = useHumanitySignals;
3042
- //# sourceMappingURL=chunk-QLWBBCI5.cjs.map
3043
+ //# sourceMappingURL=chunk-Q2ITV3NY.cjs.map