@flamingo-stack/openframe-frontend-core 0.0.588 → 0.0.589-1973.5571.1

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 (161) hide show
  1. package/dist/chat-protocol/events.d.ts +8 -2
  2. package/dist/chat-protocol/events.d.ts.map +1 -1
  3. package/dist/chat-protocol/index.cjs +188 -0
  4. package/dist/chat-protocol/index.cjs.map +1 -1
  5. package/dist/chat-protocol/index.js +188 -0
  6. package/dist/chat-protocol/index.js.map +1 -1
  7. package/dist/chat-protocol/nats-decoder.d.ts.map +1 -1
  8. package/dist/chat-protocol/source-metadata.d.ts +5 -0
  9. package/dist/chat-protocol/source-metadata.d.ts.map +1 -0
  10. package/dist/{chunk-GKPY7VLK.js → chunk-47NIV4FC.js} +4 -4
  11. package/dist/{chunk-D7YISHXC.js → chunk-4MVGFYRE.js} +534 -190
  12. package/dist/chunk-4MVGFYRE.js.map +1 -0
  13. package/dist/{chunk-I5CJJFEJ.js → chunk-4VDGIODG.js} +3 -3
  14. package/dist/{chunk-PAUJQV3C.cjs → chunk-627JVTID.cjs} +15 -15
  15. package/dist/{chunk-PAUJQV3C.cjs.map → chunk-627JVTID.cjs.map} +1 -1
  16. package/dist/{chunk-3OCYNMZ5.js → chunk-CQNILR7U.js} +3 -3
  17. package/dist/{chunk-BONXSRHZ.cjs → chunk-DPQGO4PH.cjs} +21 -21
  18. package/dist/{chunk-BONXSRHZ.cjs.map → chunk-DPQGO4PH.cjs.map} +1 -1
  19. package/dist/{chunk-A6X7EXP6.cjs → chunk-DVAAXDZV.cjs} +10 -10
  20. package/dist/{chunk-A6X7EXP6.cjs.map → chunk-DVAAXDZV.cjs.map} +1 -1
  21. package/dist/{chunk-CN36E4PK.js → chunk-EJEHIAOV.js} +2 -2
  22. package/dist/{chunk-G45RGWHF.cjs → chunk-EKKR5ZFB.cjs} +887 -543
  23. package/dist/chunk-EKKR5ZFB.cjs.map +1 -0
  24. package/dist/{chunk-IYDHV2CS.cjs → chunk-FIEOWSXH.cjs} +43 -43
  25. package/dist/{chunk-IYDHV2CS.cjs.map → chunk-FIEOWSXH.cjs.map} +1 -1
  26. package/dist/{chunk-CJOWLRUU.js → chunk-G4ND6ZZK.js} +2 -2
  27. package/dist/{chunk-KWTV3KGY.cjs → chunk-GDHQTVBP.cjs} +9 -9
  28. package/dist/{chunk-KWTV3KGY.cjs.map → chunk-GDHQTVBP.cjs.map} +1 -1
  29. package/dist/{chunk-ADANP3ZA.cjs → chunk-GOHHFX4O.cjs} +33 -33
  30. package/dist/{chunk-ADANP3ZA.cjs.map → chunk-GOHHFX4O.cjs.map} +1 -1
  31. package/dist/{chunk-KVNGYFFP.js → chunk-GU5TWGAJ.js} +3 -3
  32. package/dist/{chunk-EGYX55HT.cjs → chunk-HQKWJFFX.cjs} +159 -159
  33. package/dist/{chunk-EGYX55HT.cjs.map → chunk-HQKWJFFX.cjs.map} +1 -1
  34. package/dist/{chunk-NXZC6UTN.js → chunk-HZ6VUTLA.js} +2 -2
  35. package/dist/{chunk-WUCYI3BC.cjs → chunk-KH5KDKCE.cjs} +24 -24
  36. package/dist/{chunk-WUCYI3BC.cjs.map → chunk-KH5KDKCE.cjs.map} +1 -1
  37. package/dist/{chunk-JDINUWND.cjs → chunk-MQFA5DXU.cjs} +69 -69
  38. package/dist/{chunk-JDINUWND.cjs.map → chunk-MQFA5DXU.cjs.map} +1 -1
  39. package/dist/{chunk-5CHW7AMP.cjs → chunk-NHW2AM2X.cjs} +10 -10
  40. package/dist/{chunk-5CHW7AMP.cjs.map → chunk-NHW2AM2X.cjs.map} +1 -1
  41. package/dist/{chunk-KK3XU6YJ.cjs → chunk-NIHPN72N.cjs} +4 -4
  42. package/dist/{chunk-KK3XU6YJ.cjs.map → chunk-NIHPN72N.cjs.map} +1 -1
  43. package/dist/{chunk-EGUU3QCU.cjs → chunk-NNRRRWCD.cjs} +3 -3
  44. package/dist/{chunk-EGUU3QCU.cjs.map → chunk-NNRRRWCD.cjs.map} +1 -1
  45. package/dist/{chunk-4ZKAMPBX.js → chunk-OAY342OE.js} +2 -2
  46. package/dist/{chunk-TOYHZHTK.js → chunk-PDFH7CXB.js} +2 -2
  47. package/dist/{chunk-NIDPX52C.js → chunk-QIHBS5PM.js} +5 -5
  48. package/dist/{chunk-CNKVEVUN.js → chunk-SPOIDC23.js} +5 -5
  49. package/dist/{chunk-MENQLD57.cjs → chunk-T5FJOWHA.cjs} +5 -5
  50. package/dist/{chunk-MENQLD57.cjs.map → chunk-T5FJOWHA.cjs.map} +1 -1
  51. package/dist/{chunk-5FNWOF46.js → chunk-WZZSFOWG.js} +6 -6
  52. package/dist/{chunk-FVRRYJ6P.cjs → chunk-XIKSZSHC.cjs} +22 -22
  53. package/dist/{chunk-FVRRYJ6P.cjs.map → chunk-XIKSZSHC.cjs.map} +1 -1
  54. package/dist/{chunk-C6RJRSIM.cjs → chunk-XTMNO7MC.cjs} +3 -3
  55. package/dist/{chunk-C6RJRSIM.cjs.map → chunk-XTMNO7MC.cjs.map} +1 -1
  56. package/dist/{chunk-FQBR6SCA.js → chunk-XW4GOMK6.js} +3 -3
  57. package/dist/{chunk-77Y7R243.js → chunk-YEEMXD25.js} +5 -5
  58. package/dist/{chunk-LGBNHWDN.js → chunk-YTVTZ7JO.js} +3 -3
  59. package/dist/components/case-studies/index.cjs +9 -9
  60. package/dist/components/case-studies/index.js +3 -3
  61. package/dist/components/chat/chat-message-enhanced.d.ts.map +1 -1
  62. package/dist/components/chat/chat-message-list.d.ts.map +1 -1
  63. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  64. package/dist/components/chat/entity-cards/dispatch.d.ts.map +1 -1
  65. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +4 -1
  66. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  67. package/dist/components/chat/hooks/use-slash-commands.d.ts +3 -3
  68. package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
  69. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +2 -28
  70. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  71. package/dist/components/chat/index.cjs +2 -2
  72. package/dist/components/chat/index.js +1 -1
  73. package/dist/components/chat/remark-strip-citations.d.ts.map +1 -1
  74. package/dist/components/chat/stream/chat-stream-reducer.d.ts.map +1 -1
  75. package/dist/components/chat/tool-execution-display.d.ts.map +1 -1
  76. package/dist/components/chat/types/component.types.d.ts +4 -0
  77. package/dist/components/chat/types/component.types.d.ts.map +1 -1
  78. package/dist/components/chat/types/message.types.d.ts +35 -1
  79. package/dist/components/chat/types/message.types.d.ts.map +1 -1
  80. package/dist/components/chat/types/unified-chat-state.types.d.ts +3 -3
  81. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  82. package/dist/components/chat/utils/chat-source-metadata.d.ts +5 -0
  83. package/dist/components/chat/utils/chat-source-metadata.d.ts.map +1 -0
  84. package/dist/components/chat/utils/process-historical-messages.d.ts.map +1 -1
  85. package/dist/components/contact/index.cjs +4 -4
  86. package/dist/components/contact/index.js +3 -3
  87. package/dist/components/docs/index.cjs +7 -7
  88. package/dist/components/docs/index.js +6 -6
  89. package/dist/components/embeds/index.cjs +4 -4
  90. package/dist/components/embeds/index.js +3 -3
  91. package/dist/components/faq/index.cjs +4 -4
  92. package/dist/components/faq/index.js +3 -3
  93. package/dist/components/features/index.cjs +3 -3
  94. package/dist/components/features/index.js +2 -2
  95. package/dist/components/help-center-pages/index.cjs +23 -23
  96. package/dist/components/help-center-pages/index.js +14 -14
  97. package/dist/components/index.cjs +36 -36
  98. package/dist/components/index.js +13 -13
  99. package/dist/components/meeting-scheduler/index.cjs +21 -21
  100. package/dist/components/meeting-scheduler/index.js +2 -2
  101. package/dist/components/navigation/index.cjs +6 -6
  102. package/dist/components/navigation/index.js +5 -5
  103. package/dist/components/onboarding-guides/index.cjs +6 -6
  104. package/dist/components/onboarding-guides/index.js +5 -5
  105. package/dist/components/tickets/index.cjs +7 -7
  106. package/dist/components/tickets/index.js +6 -6
  107. package/dist/components/ui/hover-dropdown.d.ts.map +1 -1
  108. package/dist/components/ui/index.cjs +3 -3
  109. package/dist/components/ui/index.js +2 -2
  110. package/dist/index.cjs +7 -7
  111. package/dist/index.js +6 -6
  112. package/dist/utils/index.cjs.map +1 -1
  113. package/dist/utils/index.js.map +1 -1
  114. package/package.json +2 -1
  115. package/src/chat-protocol/__tests__/nats-decoder-golden.test.ts +130 -0
  116. package/src/chat-protocol/__tests__/source-metadata.test.ts +74 -0
  117. package/src/chat-protocol/events.ts +9 -1
  118. package/src/chat-protocol/nats-decoder.ts +7 -0
  119. package/src/chat-protocol/source-metadata.ts +202 -0
  120. package/src/components/chat/__tests__/chat-message-list.test.tsx +70 -0
  121. package/src/components/chat/__tests__/chat-message-streaming-memo.test.tsx +86 -0
  122. package/src/components/chat/__tests__/embeddable-chat-sources.test.tsx +411 -0
  123. package/src/components/chat/__tests__/remark-strip-citations.test.ts +52 -0
  124. package/src/components/chat/__tests__/tool-execution-display.test.tsx +93 -0
  125. package/src/components/chat/chat-message-enhanced.tsx +28 -13
  126. package/src/components/chat/chat-message-list.tsx +27 -22
  127. package/src/components/chat/embeddable-chat.tsx +55 -36
  128. package/src/components/chat/entity-cards/__tests__/dispatch-video-refs.test.tsx +74 -0
  129. package/src/components/chat/entity-cards/dispatch.tsx +86 -17
  130. package/src/components/chat/hooks/__tests__/use-slash-commands.test.ts +64 -0
  131. package/src/components/chat/hooks/use-nats-chat-adapter.ts +7 -0
  132. package/src/components/chat/hooks/use-slash-commands.ts +9 -5
  133. package/src/components/chat/hooks/use-sse-chat-adapter.ts +2 -29
  134. package/src/components/chat/remark-strip-citations.ts +5 -3
  135. package/src/components/chat/stream/__tests__/chat-stream-reducer.test.ts +56 -0
  136. package/src/components/chat/stream/chat-stream-reducer.ts +31 -0
  137. package/src/components/chat/tool-execution-display.tsx +5 -2
  138. package/src/components/chat/types/component.types.ts +4 -0
  139. package/src/components/chat/types/message.types.ts +39 -0
  140. package/src/components/chat/types/unified-chat-state.types.ts +4 -3
  141. package/src/components/chat/utils/__tests__/process-historical-messages-golden.test.ts +57 -0
  142. package/src/components/chat/utils/chat-source-metadata.ts +19 -0
  143. package/src/components/chat/utils/process-historical-messages.ts +19 -1
  144. package/src/components/ui/hover-dropdown.tsx +8 -3
  145. package/dist/chunk-D7YISHXC.js.map +0 -1
  146. package/dist/chunk-G45RGWHF.cjs.map +0 -1
  147. /package/dist/{chunk-GKPY7VLK.js.map → chunk-47NIV4FC.js.map} +0 -0
  148. /package/dist/{chunk-I5CJJFEJ.js.map → chunk-4VDGIODG.js.map} +0 -0
  149. /package/dist/{chunk-3OCYNMZ5.js.map → chunk-CQNILR7U.js.map} +0 -0
  150. /package/dist/{chunk-CN36E4PK.js.map → chunk-EJEHIAOV.js.map} +0 -0
  151. /package/dist/{chunk-CJOWLRUU.js.map → chunk-G4ND6ZZK.js.map} +0 -0
  152. /package/dist/{chunk-KVNGYFFP.js.map → chunk-GU5TWGAJ.js.map} +0 -0
  153. /package/dist/{chunk-NXZC6UTN.js.map → chunk-HZ6VUTLA.js.map} +0 -0
  154. /package/dist/{chunk-4ZKAMPBX.js.map → chunk-OAY342OE.js.map} +0 -0
  155. /package/dist/{chunk-TOYHZHTK.js.map → chunk-PDFH7CXB.js.map} +0 -0
  156. /package/dist/{chunk-NIDPX52C.js.map → chunk-QIHBS5PM.js.map} +0 -0
  157. /package/dist/{chunk-CNKVEVUN.js.map → chunk-SPOIDC23.js.map} +0 -0
  158. /package/dist/{chunk-5FNWOF46.js.map → chunk-WZZSFOWG.js.map} +0 -0
  159. /package/dist/{chunk-FQBR6SCA.js.map → chunk-XW4GOMK6.js.map} +0 -0
  160. /package/dist/{chunk-77Y7R243.js.map → chunk-YEEMXD25.js.map} +0 -0
  161. /package/dist/{chunk-LGBNHWDN.js.map → chunk-YTVTZ7JO.js.map} +0 -0
@@ -7,7 +7,7 @@ import {
7
7
  StatusFilterComponent,
8
8
  TASK_TYPE_LABELS,
9
9
  TASK_TYPE_TEXT_COLORS
10
- } from "./chunk-4ZKAMPBX.js";
10
+ } from "./chunk-OAY342OE.js";
11
11
  import {
12
12
  usePathname,
13
13
  useRouter,
@@ -267,4 +267,4 @@ export {
267
267
  DevCardRowSkeletonList,
268
268
  DeliveryRow
269
269
  };
270
- //# sourceMappingURL=chunk-CJOWLRUU.js.map
270
+ //# sourceMappingURL=chunk-G4ND6ZZK.js.map
@@ -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 _chunkEGYX55HTcjs = require('./chunk-EGYX55HT.cjs');
4
+ var _chunkHQKWJFFXcjs = require('./chunk-HQKWJFFX.cjs');
5
5
 
6
6
 
7
7
  var _chunkTRC37VRCcjs = require('./chunk-TRC37VRC.cjs');
@@ -10,7 +10,7 @@ var _chunkTRC37VRCcjs = require('./chunk-TRC37VRC.cjs');
10
10
 
11
11
 
12
12
 
13
- var _chunkG45RGWHFcjs = require('./chunk-G45RGWHF.cjs');
13
+ var _chunkEKKR5ZFBcjs = require('./chunk-EKKR5ZFB.cjs');
14
14
 
15
15
 
16
16
  var _chunkZI27ALYXcjs = require('./chunk-ZI27ALYX.cjs');
@@ -37,7 +37,7 @@ var _jsxruntime = require('react/jsx-runtime');
37
37
  function DocSearchResultRow({ result, isHighlighted }) {
38
38
  const docType = _optionalChain([result, 'access', _ => _.metadata, 'optionalAccess', _2 => _2.documentType]) || void 0;
39
39
  const sourceRepo = _optionalChain([result, 'access', _3 => _3.metadata, 'optionalAccess', _4 => _4.sourceRepo]) || void 0;
40
- const { Icon: SourceIcon, label: iconLabel } = _chunkG45RGWHFcjs.resolveSourceIcon.call(void 0, {
40
+ const { Icon: SourceIcon, label: iconLabel } = _chunkEKKR5ZFBcjs.resolveSourceIcon.call(void 0, {
41
41
  sourceRepo,
42
42
  documentType: docType
43
43
  });
@@ -67,7 +67,7 @@ function DocSearchBar({
67
67
  renderResult
68
68
  }) {
69
69
  return /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
70
- _chunkEGYX55HTcjs.SearchInput,
70
+ _chunkHQKWJFFXcjs.SearchInput,
71
71
  {
72
72
  placeholder,
73
73
  value: query,
@@ -322,27 +322,27 @@ function useDocSearch(config) {
322
322
  case "navigate-same-tab": {
323
323
  if (wantsNewTab) {
324
324
  setKeepOpen(true);
325
- window.open(action.href, "_blank", _chunkG45RGWHFcjs.NEW_TAB_FEATURES);
325
+ window.open(action.href, "_blank", _chunkEKKR5ZFBcjs.NEW_TAB_FEATURES);
326
326
  return;
327
327
  }
328
328
  setKeepOpen(false);
329
329
  const path = baseRoute && action.href.startsWith(`${baseRoute}/`) ? action.href.slice(baseRoute.length + 1) : null;
330
330
  if (path && _optionalChain([onInPageSwap, 'optionalCall', _16 => _16(path)])) return;
331
- router.push(_chunkG45RGWHFcjs.stripSameOriginToPath.call(void 0, action.href));
331
+ router.push(_chunkEKKR5ZFBcjs.stripSameOriginToPath.call(void 0, action.href));
332
332
  return;
333
333
  }
334
334
  case "navigate-new-tab":
335
335
  setKeepOpen(true);
336
336
  if (_optionalChain([runtime, 'optionalAccess', _17 => _17.navigation, 'access', _18 => _18.mode]) === "embed") {
337
337
  const targetPlatform = _nullishCoalesce(_optionalChain([result, 'access', _19 => _19.metadata, 'optionalAccess', _20 => _20.targetPlatform]), () => ( null));
338
- _chunkG45RGWHFcjs.resolveExternalNavigation.call(void 0, {
338
+ _chunkEKKR5ZFBcjs.resolveExternalNavigation.call(void 0, {
339
339
  href: action.href,
340
340
  targetPlatform,
341
341
  runtime
342
342
  }).open();
343
343
  return;
344
344
  }
345
- window.open(action.href, "_blank", _chunkG45RGWHFcjs.NEW_TAB_FEATURES);
345
+ window.open(action.href, "_blank", _chunkEKKR5ZFBcjs.NEW_TAB_FEATURES);
346
346
  return;
347
347
  case "ask-ai":
348
348
  setKeepOpen(false);
@@ -381,4 +381,4 @@ function useDocSearch(config) {
381
381
 
382
382
 
383
383
  exports.formatRelativePath = formatRelativePath; exports.DocSearchResultRow = DocSearchResultRow; exports.DocSearchBar = DocSearchBar; exports.mapDocSearchResults = mapDocSearchResults; exports.resolveSearchResultAction = resolveSearchResultAction; exports.useDocSearch = useDocSearch;
384
- //# sourceMappingURL=chunk-KWTV3KGY.cjs.map
384
+ //# sourceMappingURL=chunk-GDHQTVBP.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-KWTV3KGY.cjs","../src/components/shared/doc-search/format-relative-path.ts","../src/components/shared/doc-search/doc-search-result-row.tsx","../src/components/shared/doc-search/doc-search-bar.tsx","../src/components/shared/doc-search/map-doc-search-results.ts","../src/components/shared/doc-search/resolve-search-result-action.ts","../src/components/shared/doc-search/use-doc-search.ts"],"names":["jsx"],"mappings":"AAAA,6rBAAY;AACZ;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACA;AACA;AACA;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACE;AACF,wDAA6B;AAC7B;AACA;AClBO,SAAS,kBAAA,CAAmB,QAAA,EAA0B;AAC3D,EAAA,GAAA,CAAI,CAAC,QAAA,EAAU,OAAO,EAAA;AACtB,EAAA,MAAM,SAAA,EAAW,QAAA,CAAS,OAAA,CAAQ,OAAA,EAAS,EAAE,CAAA,CAAE,KAAA,CAAM,GAAG,CAAA;AAExD,EAAA,MAAM,eAAA,EAAiB,QAAA,CAAS,OAAA,EAAS,EAAA,EAAI,QAAA,CAAS,KAAA,CAAM,CAAA,EAAG,CAAA,CAAE,EAAA,EAAI,QAAA;AACrE,EAAA,OAAO,cAAA,CAAe,GAAA,CAAI,CAAA,GAAA,EAAA,GAAO,GAAA,CAAI,MAAA,CAAO,CAAC,CAAA,CAAE,WAAA,CAAY,EAAA,EAAI,GAAA,CAAI,KAAA,CAAM,CAAC,CAAA,CAAE,OAAA,CAAQ,IAAA,EAAM,GAAG,CAAC,CAAA,CAAE,IAAA,CAAK,KAAK,CAAA;AAC5G;ADmBA;AACA;AEcQ,+CAAA;AAZD,SAAS,kBAAA,CAAmB,EAAE,MAAA,EAAQ,cAAc,CAAA,EAA4B;AACrF,EAAA,MAAM,QAAA,kBAAW,MAAA,mBAAO,QAAA,6BAAU,eAAA,GAA2B,KAAA,CAAA;AAC7D,EAAA,MAAM,WAAA,kBAAc,MAAA,qBAAO,QAAA,6BAAU,aAAA,GAAyB,KAAA,CAAA;AAC9D,EAAA,MAAM,EAAE,IAAA,EAAM,UAAA,EAAY,KAAA,EAAO,UAAU,EAAA,EAAI,iDAAA;AAAkB,IAC/D,UAAA;AAAA,IACA,YAAA,EAAc;AAAA,EAChB,CAAC,CAAA;AACD,EAAA,MAAM,QAAA,kBAAU,MAAA,qBAAO,QAAA,6BAAU,SAAA;AAEjC,EAAA,uBACE,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,wCAAA,EACb,QAAA,EAAA;AAAA,oBAAA,6BAAA,MAAC,EAAA,EAAK,SAAA,EAAU,uCAAA,EAAwC,KAAA,EAAO,SAAA,EAC7D,QAAA,kBAAA,6BAAA,UAAC,EAAA,EAAW,SAAA,EAAU,SAAA,CAAS,EAAA,CACjC,CAAA;AAAA,oBACA,8BAAA,KAAC,EAAA,EAAI,SAAA,EAAU,gBAAA,EACb,QAAA,EAAA;AAAA,sBAAA,6BAAA,KAAC,EAAA,EAAI,SAAA,EAAW,CAAA,iBAAA,EAAoB,cAAA,EAAgB,kBAAA,EAAoB,uBAAuB,CAAA,CAAA;AAI9E,MAAA;AAEnB,IAAA;AACF,EAAA;AAEJ;AFR8H;AACA;AGwC1F;AA3BP;AAC3B,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACiB,EAAA;AACJ,EAAA;AACD,EAAA;AACZ,EAAA;AACoB;AAElBA,EAAAA;AAAC,IAAA;AAAA,IAAA;AACC,MAAA;AACO,MAAA;AACG,MAAA;AACV,MAAA;AACA,MAAA;AACA,MAAA;AAC8B,MAAA;AAC9B,MAAA;AACA,MAAA;AACA,MAAA;AAGkE,MAAA;AAA8B,IAAA;AAElG,EAAA;AAEJ;AHZ8H;AACA;AIpE3F;AACjC,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACA,EAAA;AACD;AAE6C;AACrB,EAAA;AACL,EAAA;AACD,EAAA;AACU,EAAA;AACJ,EAAA;AACT,EAAA;AACM,EAAA;AACJ,EAAA;AACN,EAAA;AACF,EAAA;AACE,EAAA;AACZ;AAE6E;AACnB,EAAA;AAGwC,EAAA;AAC9D,EAAA;AAEV,EAAA;AACwC,IAAA;AACT,MAAA;AACrC,MAAA;AACwB,MAAA;AACF,MAAA;AACN,QAAA;AACuB,QAAA;AACrD,MAAA;AACK,IAAA;AAC0B,MAAA;AACjC,IAAA;AACF,EAAA;AAEiC,EAAA;AACN,EAAA;AACI,IAAA;AAIa,MAAA;AACR,MAAA;AACiB,MAAA;AACpC,MAAA;AACY,QAAA;AACoD,QAAA;AAC7D,QAAA;AACR,QAAA;AACI,QAAA;AACc,UAAA;AACD,UAAA;AACH,UAAA;AACN,UAAA;AACH,UAAA;AACa,UAAA;AACZ,YAAA;AACO,YAAA;AACT,YAAA;AACQ,YAAA;AACE,YAAA;AAChB,UAAA;AACJ,QAAA;AACD,MAAA;AACI,IAAA;AACa,MAAA;AAC6C,MAAA;AAClD,MAAA;AACH,QAAA;AACG,QAAA;AACiC,QAAA;AAClC,QAAA;AACA,QAAA;AACA,QAAA;AACO,UAAA;AAC8C,UAAA;AACH,UAAA;AACiB,UAAA;AACpB,UAAA;AACZ,UAAA;AAC7C,QAAA;AACD,MAAA;AACH,IAAA;AACF,EAAA;AAEO,EAAA;AACT;AJ4D8H;AACA;AKpJ/E;AACG,EAAA;AAClD;AAmBsB;AACa,EAAA;AACR,EAAA;AACN,EAAA;AACK,EAAA;AACE,EAAA;AACT,EAAA;AACa,IAAA;AAOJ,IAAA;AACV,MAAA;AACe,MAAA;AACrB,MAAA;AACkC,MAAA;AAEpC,IAAA;AACyB,IAAA;AAKQ,IAAA;AACI,MAAA;AAC3C,IAAA;AAK8B,IAAA;AAC5B,MAAA;AACqD,MAAA;AAC5C,MAAA;AACT,MAAA;AACe,MAAA;AAChB,IAAA;AACwF,IAAA;AAC3F,EAAA;AACyC,EAAA;AAChC,IAAA;AACC,MAAA;AACE,MAAA;AACN,QAAA;AACqE,QAAA;AACvE,MAAA;AACF,IAAA;AACF,EAAA;AACiB,EAAA;AAC2B,IAAA;AAC5C,EAAA;AACsB,EAAA;AACxB;ALqH8H;AACA;AMnL7E;AAsDb;AAEgC;AACpB,EAAA;AAChD;AAGqE;AACzC,EAAA;AAC5B;AAEsF;AAC5D,EAAA;AACmB,EAAA;AAC7C;AAcwE;AACc,EAAA;AAEnD,EAAA;AACC,EAAA;AACV,IAAA;AACe,IAAA;AACA,IAAA;AAClB,IAAA;AAC2B,IAAA;AAEjB,IAAA;AACnB,IAAA;AACR,MAAA;AACA,MAAA;AACqC,MAAA;AACI,MAAA;AACU,MAAA;AACL,MAAA;AACF,MAAA;AAC0B,MAAA;AAC5B,MAAA;AACJ,MAAA;AACvC,IAAA;AACH,EAAA;AACO,EAAA;AACT;AAEyD;AAC8C,EAAA;AAC1B,EAAA;AAElD,EAAA;AAMM,EAAA;AACsD,EAAA;AAKrB,EAAA;AAE3B,EAAA;AAC4B,EAAA;AACf,EAAA;AACL,EAAA;AAMM,EAAA;AACU,EAAA;AAC3B,EAAA;AACL,IAAA;AACV,IAAA;AACM,MAAA;AACF,MAAA;AACrB,IAAA;AACF,EAAA;AAEgB,EAAA;AACU,IAAA;AAER,IAAA;AAEc,IAAA;AACV,MAAA;AACd,MAAA;AACiC,QAAA;AAC9B,UAAA;AACH,UAAA;AACO,UAAA;AACR,QAAA;AACkD,QAAA;AAEiC,QAAA;AAClE,QAAA;AAC2C,UAAA;AAC7D,QAAA;AAE0C,QAAA;AAE1B,QAAA;AAMsB,UAAA;AACoB,UAAA;AAC1D,QAAA;AACc,MAAA;AAC0B,QAAA;AACxB,QAAA;AACO,UAAA;AACvB,QAAA;AACA,MAAA;AACgB,QAAA;AACK,UAAA;AACrB,QAAA;AACF,MAAA;AACF,IAAA;AAGkB,IAAA;AAEL,IAAA;AACC,MAAA;AACd,IAAA;AAC0E,EAAA;AAID,EAAA;AAG7B,EAAA;AAEnB,EAAA;AAUpB,IAAA;AACyG,MAAA;AAavG,MAAA;AACgB,MAAA;AACO,QAAA;AACP,UAAA;AACC,YAAA;AACmC,YAAA;AACnD,YAAA;AACF,UAAA;AAOiB,UAAA;AAEkF,UAAA;AACjE,UAAA;AACY,UAAA;AAC9C,UAAA;AACF,QAAA;AACK,QAAA;AAIa,UAAA;AAQ0B,UAAA;AACiD,YAAA;AAC/D,YAAA;AACX,cAAA;AACb,cAAA;AACA,cAAA;AACM,YAAA;AACR,YAAA;AACF,UAAA;AACmD,UAAA;AACnD,UAAA;AACG,QAAA;AAMc,UAAA;AACsE,UAAA;AACvF,UAAA;AACG,QAAA;AAGc,UAAA;AACK,UAAA;AACtB,UAAA;AACG,QAAA;AACH,UAAA;AACJ,MAAA;AACF,IAAA;AACwF,IAAA;AAC1F,EAAA;AAK8D,EAAA;AAC9B,EAAA;AACL,IAAA;AACR,IAAA;AACnB,EAAA;AAEO,EAAA;AACL,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACA,IAAA;AACkB,IAAA;AACpB,EAAA;AACF;ANsB8H;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-KWTV3KGY.cjs","sourcesContent":[null,"/**\n * Format a full document path as a breadcrumb trail.\n * Shows parent folders only (excludes the last segment / filename).\n *\n * @example\n * formatRelativePath('openframe-oss-tenant/architecture/api-controllers.md')\n * // → 'Openframe oss tenant / Architecture'\n */\nexport function formatRelativePath(fullPath: string): string {\n if (!fullPath) return '';\n const segments = fullPath.replace(/\\.md$/, '').split('/');\n // Show only parent path (exclude the filename itself since the title already shows it)\n const parentSegments = segments.length > 1 ? segments.slice(0, -1) : segments;\n return parentSegments.map(seg => seg.charAt(0).toUpperCase() + seg.slice(1).replace(/-/g, ' ')).join(' / ');\n}\n","'use client';\n\n/**\n * Single row in the `<SearchInput>` dropdown — the standard layout\n * used by every doc-search-backed surface (company-hub data-room\n * search bar, onboarding-guide catalog search, …). Single source of\n * truth for the row appearance so search dropdowns are visually\n * identical everywhere.\n *\n * Resolves the source icon via the same `resolveSourceIcon()`\n * registry the inline chat-card refs use, so a row pointing at e.g.\n * an onboarding-guide surfaces the SAME `<GraduationCap>` glyph the\n * chat card surfaces — no cross-surface drift.\n */\n\nimport { resolveSourceIcon } from '../../chat/utils/source-row-cta';\nimport { formatRelativePath } from './format-relative-path';\n\n/**\n * Minimal result shape this row renders. Compatible with any\n * doc-search hook whose result type exposes `{ title?, path?,\n * metadata? }`. The two hub consumers (onboarding-guide catalog,\n * data-room sidebar) both satisfy this shape via their\n * `useDocSearch` hook result.\n */\nexport interface DocSearchResultRowEntry {\n title?: string;\n path?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface DocSearchResultRowProps {\n result: DocSearchResultRowEntry;\n isHighlighted: boolean;\n}\n\nexport function DocSearchResultRow({ result, isHighlighted }: DocSearchResultRowProps) {\n const docType = (result.metadata?.documentType as string) || undefined;\n const sourceRepo = (result.metadata?.sourceRepo as string) || undefined;\n const { Icon: SourceIcon, label: iconLabel } = resolveSourceIcon({\n sourceRepo,\n documentType: docType,\n });\n const isGroup = result.metadata?.isGroup as boolean | undefined;\n\n return (\n <div className=\"flex w-full min-w-0 items-center gap-3\">\n <span className=\"flex-shrink-0 text-ods-text-secondary\" title={iconLabel}>\n <SourceIcon className=\"size-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className={`truncate text-h6 ${isHighlighted ? 'text-ods-accent' : 'text-ods-text-primary'}`}>\n {result.title || result.path}\n </div>\n {!isGroup && result.path?.includes('/') && (\n <div className=\"mt-0.5 truncate text-ods-text-secondary text-h6\">{formatRelativePath(result.path)}</div>\n )}\n </div>\n </div>\n );\n}\n","'use client';\n\n/**\n * `<DocSearchBar>` — the canonical RAG-search dropdown surface.\n *\n * Mounted by every doc-search consumer (data-room sidebar, onboarding-\n * guide catalog, and any future surface that needs typeahead against\n * `/api/docs/search`). Wraps `<SearchInput>` with the lib's standard\n * `<DocSearchResultRow>` so the dropdown looks identical everywhere.\n *\n * ## Why a presentation component, not a \"search bar that owns its\n * own hook\"\n *\n * The data-fetching hook (`useDocSearch`) lives hub-side because it\n * depends on hub-only context (`useDocNavigation`, the rag-table-\n * config registry, the hub's `decideNewTab` helper). Moving the hook\n * would cascade ~5 more file migrations into the lib.\n *\n * Instead, the hook stays hub-side and callers pass its result into\n * this component as plain props. Both consumers shrink to ~5 lines.\n */\n\nimport type { ReactNode } from 'react';\nimport { SearchInput, type SearchResult } from '../../ui/search-input';\nimport { DocSearchResultRow } from './doc-search-result-row';\n\nexport interface DocSearchBarProps {\n placeholder: string;\n query: string;\n onQueryChange: (value: string) => void;\n /** Hook-fetched results. Reuses the lib's `<SearchInput>` `SearchResult`\n * shape directly so callers don't translate. */\n results: SearchResult[];\n isLoading: boolean;\n /** Result selection handler. Mirrors `<SearchInput>` — the second\n * `modifiers` argument is preserved so cmd-click / shift-click on\n * a result row still forces new-tab behavior. Hub `useDocSearch`\n * reads these to short-circuit to `window.open()`. */\n onResultSelect: (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean;\n ctrlKey?: boolean;\n shiftKey?: boolean;\n altKey?: boolean;\n button?: number;\n },\n ) => void;\n /** Lets the caller's hook force the dropdown open after a recent\n * internal action (e.g. result navigation). `undefined` falls back\n * to `<SearchInput>`'s built-in focus/hover heuristics. */\n showDropdown?: boolean;\n /** Defaults to 2 — matches the existing data-room and onboarding-\n * guide consumers. Override only if a surface needs different\n * typeahead semantics. */\n minQueryLength?: number;\n /** Defaults to 0 — both existing consumers debounce inside the\n * hook, not the input. */\n debounceMs?: number;\n className?: string;\n /** Optional row-renderer override. Defaults to the lib's standard\n * `<DocSearchResultRow>` (source icon + title + path breadcrumb).\n * Override only when a surface needs custom row chrome. */\n renderResult?: (result: SearchResult, isHighlighted: boolean) => ReactNode;\n}\n\nexport function DocSearchBar({\n placeholder,\n query,\n onQueryChange,\n results,\n isLoading,\n onResultSelect,\n showDropdown,\n minQueryLength = 2,\n debounceMs = 0,\n className = 'w-full',\n renderResult,\n}: DocSearchBarProps) {\n return (\n <SearchInput\n placeholder={placeholder}\n value={query}\n onChange={onQueryChange}\n results={results}\n isLoading={isLoading}\n onResultSelect={onResultSelect}\n showDropdown={showDropdown || undefined}\n debounceMs={debounceMs}\n minQueryLength={minQueryLength}\n className={className}\n renderResult={\n renderResult ??\n ((result, isHighlighted) => <DocSearchResultRow result={result} isHighlighted={isHighlighted} />)\n }\n />\n );\n}\n","/**\n * Map RAG `/api/docs/search` wire results into the `<DocSearchBar>`\n * dropdown's row shape, collapsing entity-table rows into grouped\n * results so the dropdown lists ONE \"Cap Table (12 records)\" row\n * instead of 12 individual rows.\n *\n * Pure transform — no telemetry, no navigation, no React deps. Lifted\n * from the hub's `hooks/use-docs.ts:mapDocSearchResults` (the hub's\n * `traceCompose` call was hub-only telemetry and is intentionally\n * dropped — callers that want logging can wrap this helper).\n */\n\nimport type { SearchResult } from '../../ui/search-input';\nimport type { DocSearchResult } from './types';\n\n/** Source repos that should be collapsed into grouped results in the search bar.\n * Only financial tables (all rows link to the same admin page).\n * Content tables (blog, webinar, podcast, etc.) stay individual since each has a unique URL. */\nconst SEARCH_GROUP_REPOS = new Set([\n 'financial-cap-table',\n 'financial-kpis',\n 'financial-pnl',\n 'financial-balance-sheet',\n 'financial-cash-flow',\n]);\n\nconst ENTITY_LABELS: Record<string, string> = {\n 'financial-cap-table': 'Cap Table',\n 'financial-kpis': 'Financial KPIs',\n 'financial-pnl': 'Profit & Loss',\n 'financial-balance-sheet': 'Balance Sheets',\n 'financial-cash-flow': 'Cash Flow',\n 'blog-posts': 'Blog Posts',\n 'product-releases': 'Product Releases',\n 'case-studies': 'Case Studies',\n webinars: 'Webinars',\n events: 'Events',\n podcasts: 'Podcasts',\n};\n\nexport function mapDocSearchResults(docs: DocSearchResult[]): SearchResult[] {\n const entityGroups = new Map<string, DocSearchResult[]>();\n // Track insertion order — groups appear where the FIRST row of that\n // repo appeared in the response.\n const order: Array<{ type: 'entity'; repo: string } | { type: 'doc'; doc: DocSearchResult }> = [];\n const seenRepos = new Set<string>();\n\n for (const doc of docs) {\n if (doc.sourceRepo && SEARCH_GROUP_REPOS.has(doc.sourceRepo)) {\n const group = entityGroups.get(doc.sourceRepo) || [];\n group.push(doc);\n entityGroups.set(doc.sourceRepo, group);\n if (!seenRepos.has(doc.sourceRepo)) {\n seenRepos.add(doc.sourceRepo);\n order.push({ type: 'entity', repo: doc.sourceRepo });\n }\n } else {\n order.push({ type: 'doc', doc });\n }\n }\n\n const results: SearchResult[] = [];\n for (const entry of order) {\n if (entry.type === 'entity') {\n // An 'entity' entry is only pushed AFTER its group lands in the map, so\n // a miss is unreachable — but dropping one group beats throwing away the\n // whole search dropdown if that ever stops holding.\n const rows = entityGroups.get(entry.repo);\n if (!rows || rows.length === 0) continue;\n const label = ENTITY_LABELS[entry.repo] || entry.repo;\n results.push({\n id: `group-${entry.repo}`,\n title: `${label} (${rows.length} ${rows.length === 1 ? 'record' : 'records'})`,\n path: rows[0].path,\n type: 'file',\n metadata: {\n documentType: rows[0].documentType,\n externalUrl: rows[0].externalUrl,\n sourceRepo: entry.repo,\n id: rows[0].entityId,\n isGroup: true,\n items: rows.map(r => ({\n name: r.name,\n externalUrl: r.externalUrl,\n id: r.entityId,\n sourceRepo: r.sourceRepo,\n documentType: r.documentType,\n })),\n },\n });\n } else {\n const doc = entry.doc;\n const isNonMarkdown = doc.documentType && doc.documentType !== 'markdown';\n results.push({\n id: doc.path,\n title: doc.name,\n description: isNonMarkdown ? doc.name : doc.snippet,\n path: doc.path,\n type: doc.type,\n metadata: {\n matchType: doc.matchType,\n ...(doc.documentType ? { documentType: doc.documentType } : {}),\n ...(doc.externalUrl ? { externalUrl: doc.externalUrl } : {}),\n ...(doc.targetPlatform != null ? { targetPlatform: doc.targetPlatform } : {}),\n ...(doc.sourceRepo ? { sourceRepo: doc.sourceRepo } : {}),\n ...(doc.entityId ? { id: doc.entityId } : {}),\n },\n });\n }\n }\n\n return results;\n}\n","/**\n * Resolve what should happen when the user picks a search result.\n * Returns one of five typed actions so the caller is a single switch.\n *\n * Resolution order:\n * 1. `externalUrl` present → resolve the href through the host's\n * `composeContentUrl` seam (when wired), then use `decideNewTab` to\n * choose same-tab vs new-tab against the resolved `targetPlatform`.\n * 2. Row has `id` + `sourceRepo` + `documentType` → synth an Ask-AI\n * action (entity drill-in via primary key, no URL).\n * 3. Row has only `path` → legacy navigation fallback.\n * 4. Nothing actionable → noop.\n *\n * Lifted from the hub's `hooks/use-docs.ts:resolveSearchResultAction`.\n * Pure — no React, no telemetry.\n */\n\nimport type { ComposeContentUrl } from '../../../utils/content-href';\nimport type { ChatRef } from '../../chat/chat-ref.types';\nimport { decideNewTab } from '../../chat/utils/decide-new-tab';\nimport type { SearchResult } from '../../ui/search-input';\n\n/** True for `https://…` and protocol-relative `//…`. A composed href that is\n * NOT absolute is an in-app route the host claimed via `hostedTypes` /\n * `overrides`. */\nfunction isAbsoluteHref(href: string): boolean {\n return /^(?:[a-z][a-z0-9+.-]*:)?\\/\\//i.test(href);\n}\n\nexport type SearchResultAction =\n | { kind: 'navigate-same-tab'; href: string }\n | { kind: 'navigate-new-tab'; href: string }\n | { kind: 'ask-ai'; detail: { source: string; ref: ChatRef } }\n | { kind: 'route'; path: string }\n | { kind: 'noop' };\n\nexport function resolveSearchResultAction(\n result: SearchResult,\n source: string,\n runtimeMode?: 'host' | 'embed',\n /** The host's unified content-href seam (`runtime.composeContentUrl`).\n * When wired, an entity row's RAG `externalUrl` is re-resolved through it,\n * so a type the host serves in-app (`hostedTypes`) or deep-links itself\n * (`overrides`) lands on the SAME href the catalog cards and chat cards\n * use. Omitted → the legacy verbatim-`externalUrl` behavior. */\n composeContentUrl?: ComposeContentUrl,\n): SearchResultAction {\n const meta = result.metadata ?? {};\n const externalUrl = meta.externalUrl as string | undefined;\n const rowId = meta.id as string | undefined;\n const sourceRepo = meta.sourceRepo as string | undefined;\n const documentType = meta.documentType as string | undefined;\n if (externalUrl) {\n const targetPlatform = meta.targetPlatform as string | null | undefined;\n // Unified content-href seam — the same call `resolveSourceRowCTA` makes\n // for chat rows. Hosted types relativize the slug out of `externalUrl`;\n // everything else comes back verbatim, so an unwired host (or a row with\n // no `documentType`) keeps today's behavior byte-for-byte.\n const composed =\n composeContentUrl && documentType\n ? composeContentUrl({\n type: documentType,\n identifier: rowId ?? '',\n externalUrl,\n targetPlatform: targetPlatform ?? null,\n })\n : null;\n const href = composed?.href || externalUrl;\n // A RELATIVE composed href means the host claimed this row as in-app.\n // Short-circuit BEFORE `decideNewTab`: in embed mode that helper forces\n // new-tab unconditionally, which would fire `window.open()` on a path\n // that only resolves inside this app.\n if (composed && !isAbsoluteHref(href)) {\n return { kind: 'navigate-same-tab', href };\n }\n // Same pure helper `useNavLink` and `useUnifiedNav` call — single\n // decision rule across cards, chips, and autocomplete rows. Thread\n // the caller's `source` as `currentSource` so the platform-vs-\n // platform comparison matches the hub's pre-migration behavior.\n const isNewTab = decideNewTab({\n href,\n targetPlatform: composed ? composed.targetPlatform : targetPlatform,\n surface: 'useUnifiedNav',\n runtimeMode,\n currentSource: source,\n });\n return isNewTab ? { kind: 'navigate-new-tab', href } : { kind: 'navigate-same-tab', href };\n }\n if (rowId && sourceRepo && documentType) {\n return {\n kind: 'ask-ai',\n detail: {\n source,\n ref: { type: documentType, id: rowId, title: result.title, url: null },\n },\n };\n }\n if (result.path) {\n return { kind: 'route', path: result.path };\n }\n return { kind: 'noop' };\n}\n","'use client';\n\n/**\n * `useDocSearch` — debounced RAG-search hook against `/api/docs/search`.\n *\n * Pure fetch + navigation glue. Embedders can mount this directly\n * (any host with a reverse-proxy that exposes `/api/docs/search` will\n * work). Hub callers wire it into the lib `<DocSearchBar>` for the\n * canonical typeahead dropdown.\n *\n * ## What moved from hub to lib\n *\n * Lifted from `multi-platform-hub/hooks/use-docs.ts:useDocSearch`. Two\n * hub-only concerns are now optional injection points instead of\n * direct imports:\n *\n * - `useDocNavigation()` (hub's in-page doc-tree swap) → optional\n * `onInPageSwap?: (path: string) => boolean` config callback. When\n * present and returns true, the hook treats a same-origin result\n * click as \"handled in-page\"; when absent or returns false, the\n * hook falls back to `onNavigate(path)` (`router.push` on hub,\n * `window.location.assign` on bare embedders).\n * - `traceCompose` (hub-only telemetry) → dropped. The lib has no\n * equivalent runtime-context yet; bring it back when there is one.\n *\n * Everything else (debounce, `useChatRuntime` for embed-mode short-\n * circuit, embed-shim router, the action-resolver + result-mapper) is\n * now lib-resident.\n *\n * ## Result hrefs go through the content-href seam\n *\n * A row's href is NOT the RAG `externalUrl` verbatim any more: when the\n * host wires `runtime.composeContentUrl` (or passes `composeContentUrl`\n * here), the row is re-resolved through it — the SAME seam the catalog\n * cards and the chat entity cards use. So a host that serves onboarding\n * guides at `/help-center/onboarding-guides/<slug>` gets that path from\n * the dropdown too, instead of bouncing the user to the canonical hub\n * URL. Unwired hosts keep the verbatim-`externalUrl` behavior.\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useChatRuntime } from '../../../contexts/chat-runtime-context';\nimport { useRouter } from '../../../embed-shims';\nimport { useDebounce } from '../../../hooks/ui/use-debounce';\nimport type { ComposeContentUrl } from '../../../utils/content-href';\nimport { contentFetch } from '../../../utils/embed-content-fetch';\nimport {\n resolveExternalNavigation,\n stripSameOriginToPath,\n NEW_TAB_FEATURES,\n} from '../../chat/utils/chat-nav-resolution';\nimport type { SearchResult } from '../../ui/search-input';\nimport { mapDocSearchResults } from './map-doc-search-results';\nimport { resolveSearchResultAction } from './resolve-search-result-action';\nimport type { DocSearchResult } from './types';\n\nexport interface UseDocSearchConfig {\n /** Discriminator passed to `/api/docs/search?source=` (e.g.\n * `'openframe'`). Embedders set it to whatever discriminator their\n * reverse-proxy expects. */\n source: string;\n /** Base route prefix this search lives under (e.g. `'/onboarding-guides'`).\n * When a result's href starts with `${baseRoute}/`, the hook\n * attempts the optional in-page swap path before falling through\n * to a full nav. */\n baseRoute: string;\n /** Imperative navigation fallback. Called when no override\n * (in-page swap, new-tab) applies. Hub callers pass\n * `(path) => router.push(path)`; embedders pass an equivalent. */\n onNavigate: (path: string) => void;\n /** Optional `RagTableConfig.id` list to narrow the search to specific\n * tables (e.g. `['onboarding-guides']`). Forwarded to\n * `/api/docs/search?tableIds=…` which intersects with the source's\n * standing set. */\n tableIds?: string[];\n /** Optional in-page swap callback. When the result's href is under\n * `baseRoute` AND this callback returns true, the hook treats the\n * click as handled in-page (no router push). Hub's\n * `<DocumentationSection>` wires this to\n * `useDocNavigation().navigate(path)`. */\n onInPageSwap?: (path: string) => boolean;\n /** Optional endpoint override. Defaults to `'/api/docs/search'`\n * (the hub's reverse-proxy route). Embedders with a different\n * path can override. */\n searchEndpoint?: string;\n /** Optional content-href seam override. Same fall-back chain as\n * `searchEndpoint`: prop → `runtime.composeContentUrl` → none. Wired,\n * it makes a result row honor the host's `hostedTypes` / `overrides`\n * instead of navigating to the RAG's canonical hub URL — so a row\n * lands where the catalog card for the same entity lands. */\n composeContentUrl?: ComposeContentUrl;\n}\n\n/** One shared empty array, so \"no matches\" keeps a stable identity. */\nconst NO_RESULTS: SearchResult[] = [];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** `Array.isArray` narrows `unknown` to `any[]`; this keeps the elements `unknown`. */\nfunction isUnknownArray(value: unknown): value is readonly unknown[] {\n return Array.isArray(value);\n}\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key];\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Decode `/api/docs/search`'s `{ success, data }` envelope.\n *\n * `Response.json()` is typed `any`, so the rows used to reach\n * `mapDocSearchResults` through an `as DocSearchResult[]` that asserted a\n * shape nothing had checked. `path` and `name` are the two that bite: the\n * mapper uses `path` as the row's React `id` and `name` as its title, so a row\n * missing either renders as a blank, key-colliding entry in the dropdown.\n * Such rows are dropped; the rest of the result set still lists.\n *\n * Returns null when the envelope itself is not a successful result set.\n */\nfunction toDocSearchResults(payload: unknown): DocSearchResult[] | null {\n if (!isRecord(payload) || !payload.success || !isUnknownArray(payload.data)) return null;\n\n const rows: DocSearchResult[] = [];\n for (const entry of payload.data) {\n if (!isRecord(entry)) continue;\n const path = readString(entry, 'path');\n const name = readString(entry, 'name');\n const type = entry.type;\n if (path === undefined || name === undefined) continue;\n\n const targetPlatform = entry.targetPlatform;\n rows.push({\n path,\n name,\n type: type === 'folder' ? 'folder' : 'file',\n snippet: readString(entry, 'snippet') ?? '',\n matchType: entry.matchType === 'title' ? 'title' : 'content',\n documentType: readString(entry, 'documentType'),\n externalUrl: readString(entry, 'externalUrl'),\n targetPlatform: typeof targetPlatform === 'string' ? targetPlatform : null,\n sourceRepo: readString(entry, 'sourceRepo'),\n entityId: readString(entry, 'entityId'),\n });\n }\n return rows;\n}\n\nexport function useDocSearch(config: UseDocSearchConfig) {\n const { source, baseRoute, onNavigate, tableIds, onInPageSwap, searchEndpoint, composeContentUrl } = config;\n const tableIdsKey = tableIds && tableIds.length > 0 ? tableIds.join(',') : '';\n\n const router = useRouter();\n // Optional chat-runtime read — when present and mode='embed' the\n // search-result row click short-circuits to a new-tab open against\n // the absolutized URL. Null/host preserves today's behavior.\n // Also used as the proxy-prefix fallback for `searchEndpoint`, matching\n // how tickets resolves `findTicketUrl`.\n const runtime = useChatRuntime();\n const resolvedSearchEndpoint = searchEndpoint ?? runtime?.endpoints.docsSearchUrl ?? '/api/docs/search';\n // Content-href seam, same prop → runtime → none chain. Threading it into\n // `resolveSearchResultAction` is what makes the dropdown obey the host's\n // route map; without it the row navigates to the RAG's canonical hub URL\n // even when the host serves that content in-app under a different path.\n const resolvedComposeContentUrl = composeContentUrl ?? runtime?.composeContentUrl;\n\n const [query, setQuery] = useState('');\n const [results, setResults] = useState<SearchResult[]>(NO_RESULTS);\n const [isFetching, setIsFetching] = useState(false);\n const debouncedQuery = useDebounce(query, 300);\n\n // Falling below the two-character floor empties the dropdown. Adjusted while\n // rendering (React's documented pattern for a reset the render already has\n // the answer to) rather than from an effect, which committed one more frame\n // of the previous matches under a query that no longer searches for them.\n const searchable = debouncedQuery.trim().length >= 2;\n const [wasSearchable, setWasSearchable] = useState(searchable);\n if (wasSearchable !== searchable) {\n setWasSearchable(searchable);\n if (!searchable) {\n setResults(NO_RESULTS);\n setIsFetching(false);\n }\n }\n\n useEffect(() => {\n if (!searchable) return undefined;\n\n let cancelled = false;\n\n async function fetchResults() {\n setIsFetching(true);\n try {\n const params = new URLSearchParams({\n q: debouncedQuery,\n source,\n limit: '10',\n });\n if (tableIdsKey) params.set('tableIds', tableIdsKey);\n\n const response = await contentFetch(`${resolvedSearchEndpoint}?${params.toString()}`);\n if (!response.ok) {\n throw new Error(`Search request failed: ${response.status}`);\n }\n\n const json: unknown = await response.json();\n\n if (!cancelled) {\n // An envelope that is not a successful result set now clears the\n // dropdown instead of leaving it untouched. Previously a 200 that\n // carried `success: false` (or a non-array `data`) fell through\n // every branch, so the PREVIOUS query's matches stayed on screen\n // under the new query — the throw path already reset them.\n const rows = toDocSearchResults(json);\n setResults(rows ? mapDocSearchResults(rows) : NO_RESULTS);\n }\n } catch (error) {\n console.error('Doc search error:', error);\n if (!cancelled) {\n setResults(NO_RESULTS);\n }\n } finally {\n if (!cancelled) {\n setIsFetching(false);\n }\n }\n }\n\n // Never rejects — try/catch/finally, every write gated on `cancelled`.\n void fetchResults();\n\n return () => {\n cancelled = true;\n };\n }, [debouncedQuery, searchable, source, tableIdsKey, resolvedSearchEndpoint]);\n\n // Derived loading state — single source of truth for \"should the\n // dropdown show 'Loading...' instead of 'No results found'\":\n const isLoading = query.trim().length >= 2 && (query !== debouncedQuery || isFetching);\n\n // Track whether dropdown should stay open (external link opened in new tab).\n const [keepOpen, setKeepOpen] = useState(false);\n\n const handleResultSelect = useCallback(\n (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean;\n ctrlKey?: boolean;\n shiftKey?: boolean;\n altKey?: boolean;\n button?: number;\n },\n ) => {\n const action = resolveSearchResultAction(result, source, runtime?.navigation.mode, resolvedComposeContentUrl);\n // Modifier / non-primary mouse click → force new tab regardless of\n // same-tab/new-tab decision. The dropdown row is a `<div>`, not an\n // `<a target=\"_blank\">`, so the browser doesn't background-tab\n // natively on cmd-click. Honor it explicitly here for parity with\n // the anchor-based surfaces (cards, chips, related-content). Plain\n // Enter from the keyboard passes `modifiers === undefined`.\n const wantsNewTab =\n modifiers &&\n (modifiers.metaKey ||\n modifiers.ctrlKey ||\n modifiers.shiftKey ||\n modifiers.altKey ||\n (typeof modifiers.button === 'number' && modifiers.button !== 0));\n switch (action.kind) {\n case 'navigate-same-tab': {\n if (wantsNewTab) {\n setKeepOpen(true);\n window.open(action.href, '_blank', NEW_TAB_FEATURES);\n return;\n }\n // Same-origin click:\n // 1. If the href is under the current doc-tree's baseRoute AND\n // an `onInPageSwap` callback is wired AND returns true →\n // consider in-page swap handled.\n // 2. Otherwise → embed-shim `router.push()` (soft RSC nav on\n // Next.js hosts, window.location.assign on bare hosts).\n setKeepOpen(false);\n const path =\n baseRoute && action.href.startsWith(`${baseRoute}/`) ? action.href.slice(baseRoute.length + 1) : null;\n if (path && onInPageSwap?.(path)) return;\n router.push(stripSameOriginToPath(action.href));\n return;\n }\n case 'navigate-new-tab':\n // Cross-origin (e.g. clicking a flamingo.run release from\n // product-hub) — open in a new tab. Keep dropdown open so the\n // user can pick another result without re-searching.\n setKeepOpen(true);\n // Embed-mode short-circuit — the row was clicked while the panel is\n // hosted inside an embedding app, so absolutize against the\n // embedder-supplied content origin and honor its `openExternal`\n // override. Not the only path an embed host takes: a composed\n // RELATIVE href resolves to `navigate-same-tab` above and never\n // reaches `decideNewTab`, which is what lets an embedder keep its\n // own in-app routes in-app.\n if (runtime?.navigation.mode === 'embed') {\n const targetPlatform = (result.metadata?.targetPlatform as string | null | undefined) ?? null;\n resolveExternalNavigation({\n href: action.href,\n targetPlatform,\n runtime,\n }).open();\n return;\n }\n window.open(action.href, '_blank', NEW_TAB_FEATURES);\n return;\n case 'ask-ai':\n // Row is searchable-but-not-openable (cap_table positions,\n // financial-kpi snapshots, anything backed by\n // `resolveUrl: () => null`). Dispatch a CustomEvent that\n // GlobalAskAI listens for — opens chat + drills via\n // `entityIdFilter` (primary-key only, same as inline-card Ask).\n setKeepOpen(false);\n window.dispatchEvent(new CustomEvent('ask-ai:open-with-ref', { detail: action.detail }));\n return;\n case 'route':\n // Final fallback: legacy navigation by path. Hits when a row\n // has neither URL nor pk metadata — a mapper/API regression.\n setKeepOpen(false);\n onNavigate(action.path);\n return;\n case 'noop':\n return;\n }\n },\n [onNavigate, source, baseRoute, router, onInPageSwap, runtime, resolvedComposeContentUrl],\n );\n\n // Reset keepOpen when query changes. Adjusted while rendering, not from an\n // effect: the dropdown's open state is read from this in the same render the\n // keystroke produced, so an effect held it pinned open for one extra frame.\n const [keepOpenForQuery, setKeepOpenForQuery] = useState(query);\n if (keepOpenForQuery !== query) {\n setKeepOpenForQuery(query);\n setKeepOpen(false);\n }\n\n return {\n query,\n setQuery,\n results,\n isLoading,\n handleResultSelect,\n keepDropdownOpen: keepOpen,\n };\n}\n"]}
1
+ {"version":3,"sources":["/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-GDHQTVBP.cjs","../src/components/shared/doc-search/format-relative-path.ts","../src/components/shared/doc-search/doc-search-result-row.tsx","../src/components/shared/doc-search/doc-search-bar.tsx","../src/components/shared/doc-search/map-doc-search-results.ts","../src/components/shared/doc-search/resolve-search-result-action.ts","../src/components/shared/doc-search/use-doc-search.ts"],"names":["jsx"],"mappings":"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","file":"/home/runner/work/openframe-oss-lib/openframe-oss-lib/openframe-frontend-core/dist/chunk-GDHQTVBP.cjs","sourcesContent":[null,"/**\n * Format a full document path as a breadcrumb trail.\n * Shows parent folders only (excludes the last segment / filename).\n *\n * @example\n * formatRelativePath('openframe-oss-tenant/architecture/api-controllers.md')\n * // → 'Openframe oss tenant / Architecture'\n */\nexport function formatRelativePath(fullPath: string): string {\n if (!fullPath) return '';\n const segments = fullPath.replace(/\\.md$/, '').split('/');\n // Show only parent path (exclude the filename itself since the title already shows it)\n const parentSegments = segments.length > 1 ? segments.slice(0, -1) : segments;\n return parentSegments.map(seg => seg.charAt(0).toUpperCase() + seg.slice(1).replace(/-/g, ' ')).join(' / ');\n}\n","'use client';\n\n/**\n * Single row in the `<SearchInput>` dropdown — the standard layout\n * used by every doc-search-backed surface (company-hub data-room\n * search bar, onboarding-guide catalog search, …). Single source of\n * truth for the row appearance so search dropdowns are visually\n * identical everywhere.\n *\n * Resolves the source icon via the same `resolveSourceIcon()`\n * registry the inline chat-card refs use, so a row pointing at e.g.\n * an onboarding-guide surfaces the SAME `<GraduationCap>` glyph the\n * chat card surfaces — no cross-surface drift.\n */\n\nimport { resolveSourceIcon } from '../../chat/utils/source-row-cta';\nimport { formatRelativePath } from './format-relative-path';\n\n/**\n * Minimal result shape this row renders. Compatible with any\n * doc-search hook whose result type exposes `{ title?, path?,\n * metadata? }`. The two hub consumers (onboarding-guide catalog,\n * data-room sidebar) both satisfy this shape via their\n * `useDocSearch` hook result.\n */\nexport interface DocSearchResultRowEntry {\n title?: string;\n path?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface DocSearchResultRowProps {\n result: DocSearchResultRowEntry;\n isHighlighted: boolean;\n}\n\nexport function DocSearchResultRow({ result, isHighlighted }: DocSearchResultRowProps) {\n const docType = (result.metadata?.documentType as string) || undefined;\n const sourceRepo = (result.metadata?.sourceRepo as string) || undefined;\n const { Icon: SourceIcon, label: iconLabel } = resolveSourceIcon({\n sourceRepo,\n documentType: docType,\n });\n const isGroup = result.metadata?.isGroup as boolean | undefined;\n\n return (\n <div className=\"flex w-full min-w-0 items-center gap-3\">\n <span className=\"flex-shrink-0 text-ods-text-secondary\" title={iconLabel}>\n <SourceIcon className=\"size-4\" />\n </span>\n <div className=\"min-w-0 flex-1\">\n <div className={`truncate text-h6 ${isHighlighted ? 'text-ods-accent' : 'text-ods-text-primary'}`}>\n {result.title || result.path}\n </div>\n {!isGroup && result.path?.includes('/') && (\n <div className=\"mt-0.5 truncate text-ods-text-secondary text-h6\">{formatRelativePath(result.path)}</div>\n )}\n </div>\n </div>\n );\n}\n","'use client';\n\n/**\n * `<DocSearchBar>` — the canonical RAG-search dropdown surface.\n *\n * Mounted by every doc-search consumer (data-room sidebar, onboarding-\n * guide catalog, and any future surface that needs typeahead against\n * `/api/docs/search`). Wraps `<SearchInput>` with the lib's standard\n * `<DocSearchResultRow>` so the dropdown looks identical everywhere.\n *\n * ## Why a presentation component, not a \"search bar that owns its\n * own hook\"\n *\n * The data-fetching hook (`useDocSearch`) lives hub-side because it\n * depends on hub-only context (`useDocNavigation`, the rag-table-\n * config registry, the hub's `decideNewTab` helper). Moving the hook\n * would cascade ~5 more file migrations into the lib.\n *\n * Instead, the hook stays hub-side and callers pass its result into\n * this component as plain props. Both consumers shrink to ~5 lines.\n */\n\nimport type { ReactNode } from 'react';\nimport { SearchInput, type SearchResult } from '../../ui/search-input';\nimport { DocSearchResultRow } from './doc-search-result-row';\n\nexport interface DocSearchBarProps {\n placeholder: string;\n query: string;\n onQueryChange: (value: string) => void;\n /** Hook-fetched results. Reuses the lib's `<SearchInput>` `SearchResult`\n * shape directly so callers don't translate. */\n results: SearchResult[];\n isLoading: boolean;\n /** Result selection handler. Mirrors `<SearchInput>` — the second\n * `modifiers` argument is preserved so cmd-click / shift-click on\n * a result row still forces new-tab behavior. Hub `useDocSearch`\n * reads these to short-circuit to `window.open()`. */\n onResultSelect: (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean;\n ctrlKey?: boolean;\n shiftKey?: boolean;\n altKey?: boolean;\n button?: number;\n },\n ) => void;\n /** Lets the caller's hook force the dropdown open after a recent\n * internal action (e.g. result navigation). `undefined` falls back\n * to `<SearchInput>`'s built-in focus/hover heuristics. */\n showDropdown?: boolean;\n /** Defaults to 2 — matches the existing data-room and onboarding-\n * guide consumers. Override only if a surface needs different\n * typeahead semantics. */\n minQueryLength?: number;\n /** Defaults to 0 — both existing consumers debounce inside the\n * hook, not the input. */\n debounceMs?: number;\n className?: string;\n /** Optional row-renderer override. Defaults to the lib's standard\n * `<DocSearchResultRow>` (source icon + title + path breadcrumb).\n * Override only when a surface needs custom row chrome. */\n renderResult?: (result: SearchResult, isHighlighted: boolean) => ReactNode;\n}\n\nexport function DocSearchBar({\n placeholder,\n query,\n onQueryChange,\n results,\n isLoading,\n onResultSelect,\n showDropdown,\n minQueryLength = 2,\n debounceMs = 0,\n className = 'w-full',\n renderResult,\n}: DocSearchBarProps) {\n return (\n <SearchInput\n placeholder={placeholder}\n value={query}\n onChange={onQueryChange}\n results={results}\n isLoading={isLoading}\n onResultSelect={onResultSelect}\n showDropdown={showDropdown || undefined}\n debounceMs={debounceMs}\n minQueryLength={minQueryLength}\n className={className}\n renderResult={\n renderResult ??\n ((result, isHighlighted) => <DocSearchResultRow result={result} isHighlighted={isHighlighted} />)\n }\n />\n );\n}\n","/**\n * Map RAG `/api/docs/search` wire results into the `<DocSearchBar>`\n * dropdown's row shape, collapsing entity-table rows into grouped\n * results so the dropdown lists ONE \"Cap Table (12 records)\" row\n * instead of 12 individual rows.\n *\n * Pure transform — no telemetry, no navigation, no React deps. Lifted\n * from the hub's `hooks/use-docs.ts:mapDocSearchResults` (the hub's\n * `traceCompose` call was hub-only telemetry and is intentionally\n * dropped — callers that want logging can wrap this helper).\n */\n\nimport type { SearchResult } from '../../ui/search-input';\nimport type { DocSearchResult } from './types';\n\n/** Source repos that should be collapsed into grouped results in the search bar.\n * Only financial tables (all rows link to the same admin page).\n * Content tables (blog, webinar, podcast, etc.) stay individual since each has a unique URL. */\nconst SEARCH_GROUP_REPOS = new Set([\n 'financial-cap-table',\n 'financial-kpis',\n 'financial-pnl',\n 'financial-balance-sheet',\n 'financial-cash-flow',\n]);\n\nconst ENTITY_LABELS: Record<string, string> = {\n 'financial-cap-table': 'Cap Table',\n 'financial-kpis': 'Financial KPIs',\n 'financial-pnl': 'Profit & Loss',\n 'financial-balance-sheet': 'Balance Sheets',\n 'financial-cash-flow': 'Cash Flow',\n 'blog-posts': 'Blog Posts',\n 'product-releases': 'Product Releases',\n 'case-studies': 'Case Studies',\n webinars: 'Webinars',\n events: 'Events',\n podcasts: 'Podcasts',\n};\n\nexport function mapDocSearchResults(docs: DocSearchResult[]): SearchResult[] {\n const entityGroups = new Map<string, DocSearchResult[]>();\n // Track insertion order — groups appear where the FIRST row of that\n // repo appeared in the response.\n const order: Array<{ type: 'entity'; repo: string } | { type: 'doc'; doc: DocSearchResult }> = [];\n const seenRepos = new Set<string>();\n\n for (const doc of docs) {\n if (doc.sourceRepo && SEARCH_GROUP_REPOS.has(doc.sourceRepo)) {\n const group = entityGroups.get(doc.sourceRepo) || [];\n group.push(doc);\n entityGroups.set(doc.sourceRepo, group);\n if (!seenRepos.has(doc.sourceRepo)) {\n seenRepos.add(doc.sourceRepo);\n order.push({ type: 'entity', repo: doc.sourceRepo });\n }\n } else {\n order.push({ type: 'doc', doc });\n }\n }\n\n const results: SearchResult[] = [];\n for (const entry of order) {\n if (entry.type === 'entity') {\n // An 'entity' entry is only pushed AFTER its group lands in the map, so\n // a miss is unreachable — but dropping one group beats throwing away the\n // whole search dropdown if that ever stops holding.\n const rows = entityGroups.get(entry.repo);\n if (!rows || rows.length === 0) continue;\n const label = ENTITY_LABELS[entry.repo] || entry.repo;\n results.push({\n id: `group-${entry.repo}`,\n title: `${label} (${rows.length} ${rows.length === 1 ? 'record' : 'records'})`,\n path: rows[0].path,\n type: 'file',\n metadata: {\n documentType: rows[0].documentType,\n externalUrl: rows[0].externalUrl,\n sourceRepo: entry.repo,\n id: rows[0].entityId,\n isGroup: true,\n items: rows.map(r => ({\n name: r.name,\n externalUrl: r.externalUrl,\n id: r.entityId,\n sourceRepo: r.sourceRepo,\n documentType: r.documentType,\n })),\n },\n });\n } else {\n const doc = entry.doc;\n const isNonMarkdown = doc.documentType && doc.documentType !== 'markdown';\n results.push({\n id: doc.path,\n title: doc.name,\n description: isNonMarkdown ? doc.name : doc.snippet,\n path: doc.path,\n type: doc.type,\n metadata: {\n matchType: doc.matchType,\n ...(doc.documentType ? { documentType: doc.documentType } : {}),\n ...(doc.externalUrl ? { externalUrl: doc.externalUrl } : {}),\n ...(doc.targetPlatform != null ? { targetPlatform: doc.targetPlatform } : {}),\n ...(doc.sourceRepo ? { sourceRepo: doc.sourceRepo } : {}),\n ...(doc.entityId ? { id: doc.entityId } : {}),\n },\n });\n }\n }\n\n return results;\n}\n","/**\n * Resolve what should happen when the user picks a search result.\n * Returns one of five typed actions so the caller is a single switch.\n *\n * Resolution order:\n * 1. `externalUrl` present → resolve the href through the host's\n * `composeContentUrl` seam (when wired), then use `decideNewTab` to\n * choose same-tab vs new-tab against the resolved `targetPlatform`.\n * 2. Row has `id` + `sourceRepo` + `documentType` → synth an Ask-AI\n * action (entity drill-in via primary key, no URL).\n * 3. Row has only `path` → legacy navigation fallback.\n * 4. Nothing actionable → noop.\n *\n * Lifted from the hub's `hooks/use-docs.ts:resolveSearchResultAction`.\n * Pure — no React, no telemetry.\n */\n\nimport type { ComposeContentUrl } from '../../../utils/content-href';\nimport type { ChatRef } from '../../chat/chat-ref.types';\nimport { decideNewTab } from '../../chat/utils/decide-new-tab';\nimport type { SearchResult } from '../../ui/search-input';\n\n/** True for `https://…` and protocol-relative `//…`. A composed href that is\n * NOT absolute is an in-app route the host claimed via `hostedTypes` /\n * `overrides`. */\nfunction isAbsoluteHref(href: string): boolean {\n return /^(?:[a-z][a-z0-9+.-]*:)?\\/\\//i.test(href);\n}\n\nexport type SearchResultAction =\n | { kind: 'navigate-same-tab'; href: string }\n | { kind: 'navigate-new-tab'; href: string }\n | { kind: 'ask-ai'; detail: { source: string; ref: ChatRef } }\n | { kind: 'route'; path: string }\n | { kind: 'noop' };\n\nexport function resolveSearchResultAction(\n result: SearchResult,\n source: string,\n runtimeMode?: 'host' | 'embed',\n /** The host's unified content-href seam (`runtime.composeContentUrl`).\n * When wired, an entity row's RAG `externalUrl` is re-resolved through it,\n * so a type the host serves in-app (`hostedTypes`) or deep-links itself\n * (`overrides`) lands on the SAME href the catalog cards and chat cards\n * use. Omitted → the legacy verbatim-`externalUrl` behavior. */\n composeContentUrl?: ComposeContentUrl,\n): SearchResultAction {\n const meta = result.metadata ?? {};\n const externalUrl = meta.externalUrl as string | undefined;\n const rowId = meta.id as string | undefined;\n const sourceRepo = meta.sourceRepo as string | undefined;\n const documentType = meta.documentType as string | undefined;\n if (externalUrl) {\n const targetPlatform = meta.targetPlatform as string | null | undefined;\n // Unified content-href seam — the same call `resolveSourceRowCTA` makes\n // for chat rows. Hosted types relativize the slug out of `externalUrl`;\n // everything else comes back verbatim, so an unwired host (or a row with\n // no `documentType`) keeps today's behavior byte-for-byte.\n const composed =\n composeContentUrl && documentType\n ? composeContentUrl({\n type: documentType,\n identifier: rowId ?? '',\n externalUrl,\n targetPlatform: targetPlatform ?? null,\n })\n : null;\n const href = composed?.href || externalUrl;\n // A RELATIVE composed href means the host claimed this row as in-app.\n // Short-circuit BEFORE `decideNewTab`: in embed mode that helper forces\n // new-tab unconditionally, which would fire `window.open()` on a path\n // that only resolves inside this app.\n if (composed && !isAbsoluteHref(href)) {\n return { kind: 'navigate-same-tab', href };\n }\n // Same pure helper `useNavLink` and `useUnifiedNav` call — single\n // decision rule across cards, chips, and autocomplete rows. Thread\n // the caller's `source` as `currentSource` so the platform-vs-\n // platform comparison matches the hub's pre-migration behavior.\n const isNewTab = decideNewTab({\n href,\n targetPlatform: composed ? composed.targetPlatform : targetPlatform,\n surface: 'useUnifiedNav',\n runtimeMode,\n currentSource: source,\n });\n return isNewTab ? { kind: 'navigate-new-tab', href } : { kind: 'navigate-same-tab', href };\n }\n if (rowId && sourceRepo && documentType) {\n return {\n kind: 'ask-ai',\n detail: {\n source,\n ref: { type: documentType, id: rowId, title: result.title, url: null },\n },\n };\n }\n if (result.path) {\n return { kind: 'route', path: result.path };\n }\n return { kind: 'noop' };\n}\n","'use client';\n\n/**\n * `useDocSearch` — debounced RAG-search hook against `/api/docs/search`.\n *\n * Pure fetch + navigation glue. Embedders can mount this directly\n * (any host with a reverse-proxy that exposes `/api/docs/search` will\n * work). Hub callers wire it into the lib `<DocSearchBar>` for the\n * canonical typeahead dropdown.\n *\n * ## What moved from hub to lib\n *\n * Lifted from `multi-platform-hub/hooks/use-docs.ts:useDocSearch`. Two\n * hub-only concerns are now optional injection points instead of\n * direct imports:\n *\n * - `useDocNavigation()` (hub's in-page doc-tree swap) → optional\n * `onInPageSwap?: (path: string) => boolean` config callback. When\n * present and returns true, the hook treats a same-origin result\n * click as \"handled in-page\"; when absent or returns false, the\n * hook falls back to `onNavigate(path)` (`router.push` on hub,\n * `window.location.assign` on bare embedders).\n * - `traceCompose` (hub-only telemetry) → dropped. The lib has no\n * equivalent runtime-context yet; bring it back when there is one.\n *\n * Everything else (debounce, `useChatRuntime` for embed-mode short-\n * circuit, embed-shim router, the action-resolver + result-mapper) is\n * now lib-resident.\n *\n * ## Result hrefs go through the content-href seam\n *\n * A row's href is NOT the RAG `externalUrl` verbatim any more: when the\n * host wires `runtime.composeContentUrl` (or passes `composeContentUrl`\n * here), the row is re-resolved through it — the SAME seam the catalog\n * cards and the chat entity cards use. So a host that serves onboarding\n * guides at `/help-center/onboarding-guides/<slug>` gets that path from\n * the dropdown too, instead of bouncing the user to the canonical hub\n * URL. Unwired hosts keep the verbatim-`externalUrl` behavior.\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport { useChatRuntime } from '../../../contexts/chat-runtime-context';\nimport { useRouter } from '../../../embed-shims';\nimport { useDebounce } from '../../../hooks/ui/use-debounce';\nimport type { ComposeContentUrl } from '../../../utils/content-href';\nimport { contentFetch } from '../../../utils/embed-content-fetch';\nimport {\n resolveExternalNavigation,\n stripSameOriginToPath,\n NEW_TAB_FEATURES,\n} from '../../chat/utils/chat-nav-resolution';\nimport type { SearchResult } from '../../ui/search-input';\nimport { mapDocSearchResults } from './map-doc-search-results';\nimport { resolveSearchResultAction } from './resolve-search-result-action';\nimport type { DocSearchResult } from './types';\n\nexport interface UseDocSearchConfig {\n /** Discriminator passed to `/api/docs/search?source=` (e.g.\n * `'openframe'`). Embedders set it to whatever discriminator their\n * reverse-proxy expects. */\n source: string;\n /** Base route prefix this search lives under (e.g. `'/onboarding-guides'`).\n * When a result's href starts with `${baseRoute}/`, the hook\n * attempts the optional in-page swap path before falling through\n * to a full nav. */\n baseRoute: string;\n /** Imperative navigation fallback. Called when no override\n * (in-page swap, new-tab) applies. Hub callers pass\n * `(path) => router.push(path)`; embedders pass an equivalent. */\n onNavigate: (path: string) => void;\n /** Optional `RagTableConfig.id` list to narrow the search to specific\n * tables (e.g. `['onboarding-guides']`). Forwarded to\n * `/api/docs/search?tableIds=…` which intersects with the source's\n * standing set. */\n tableIds?: string[];\n /** Optional in-page swap callback. When the result's href is under\n * `baseRoute` AND this callback returns true, the hook treats the\n * click as handled in-page (no router push). Hub's\n * `<DocumentationSection>` wires this to\n * `useDocNavigation().navigate(path)`. */\n onInPageSwap?: (path: string) => boolean;\n /** Optional endpoint override. Defaults to `'/api/docs/search'`\n * (the hub's reverse-proxy route). Embedders with a different\n * path can override. */\n searchEndpoint?: string;\n /** Optional content-href seam override. Same fall-back chain as\n * `searchEndpoint`: prop → `runtime.composeContentUrl` → none. Wired,\n * it makes a result row honor the host's `hostedTypes` / `overrides`\n * instead of navigating to the RAG's canonical hub URL — so a row\n * lands where the catalog card for the same entity lands. */\n composeContentUrl?: ComposeContentUrl;\n}\n\n/** One shared empty array, so \"no matches\" keeps a stable identity. */\nconst NO_RESULTS: SearchResult[] = [];\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null;\n}\n\n/** `Array.isArray` narrows `unknown` to `any[]`; this keeps the elements `unknown`. */\nfunction isUnknownArray(value: unknown): value is readonly unknown[] {\n return Array.isArray(value);\n}\n\nfunction readString(source: Record<string, unknown>, key: string): string | undefined {\n const value = source[key];\n return typeof value === 'string' ? value : undefined;\n}\n\n/**\n * Decode `/api/docs/search`'s `{ success, data }` envelope.\n *\n * `Response.json()` is typed `any`, so the rows used to reach\n * `mapDocSearchResults` through an `as DocSearchResult[]` that asserted a\n * shape nothing had checked. `path` and `name` are the two that bite: the\n * mapper uses `path` as the row's React `id` and `name` as its title, so a row\n * missing either renders as a blank, key-colliding entry in the dropdown.\n * Such rows are dropped; the rest of the result set still lists.\n *\n * Returns null when the envelope itself is not a successful result set.\n */\nfunction toDocSearchResults(payload: unknown): DocSearchResult[] | null {\n if (!isRecord(payload) || !payload.success || !isUnknownArray(payload.data)) return null;\n\n const rows: DocSearchResult[] = [];\n for (const entry of payload.data) {\n if (!isRecord(entry)) continue;\n const path = readString(entry, 'path');\n const name = readString(entry, 'name');\n const type = entry.type;\n if (path === undefined || name === undefined) continue;\n\n const targetPlatform = entry.targetPlatform;\n rows.push({\n path,\n name,\n type: type === 'folder' ? 'folder' : 'file',\n snippet: readString(entry, 'snippet') ?? '',\n matchType: entry.matchType === 'title' ? 'title' : 'content',\n documentType: readString(entry, 'documentType'),\n externalUrl: readString(entry, 'externalUrl'),\n targetPlatform: typeof targetPlatform === 'string' ? targetPlatform : null,\n sourceRepo: readString(entry, 'sourceRepo'),\n entityId: readString(entry, 'entityId'),\n });\n }\n return rows;\n}\n\nexport function useDocSearch(config: UseDocSearchConfig) {\n const { source, baseRoute, onNavigate, tableIds, onInPageSwap, searchEndpoint, composeContentUrl } = config;\n const tableIdsKey = tableIds && tableIds.length > 0 ? tableIds.join(',') : '';\n\n const router = useRouter();\n // Optional chat-runtime read — when present and mode='embed' the\n // search-result row click short-circuits to a new-tab open against\n // the absolutized URL. Null/host preserves today's behavior.\n // Also used as the proxy-prefix fallback for `searchEndpoint`, matching\n // how tickets resolves `findTicketUrl`.\n const runtime = useChatRuntime();\n const resolvedSearchEndpoint = searchEndpoint ?? runtime?.endpoints.docsSearchUrl ?? '/api/docs/search';\n // Content-href seam, same prop → runtime → none chain. Threading it into\n // `resolveSearchResultAction` is what makes the dropdown obey the host's\n // route map; without it the row navigates to the RAG's canonical hub URL\n // even when the host serves that content in-app under a different path.\n const resolvedComposeContentUrl = composeContentUrl ?? runtime?.composeContentUrl;\n\n const [query, setQuery] = useState('');\n const [results, setResults] = useState<SearchResult[]>(NO_RESULTS);\n const [isFetching, setIsFetching] = useState(false);\n const debouncedQuery = useDebounce(query, 300);\n\n // Falling below the two-character floor empties the dropdown. Adjusted while\n // rendering (React's documented pattern for a reset the render already has\n // the answer to) rather than from an effect, which committed one more frame\n // of the previous matches under a query that no longer searches for them.\n const searchable = debouncedQuery.trim().length >= 2;\n const [wasSearchable, setWasSearchable] = useState(searchable);\n if (wasSearchable !== searchable) {\n setWasSearchable(searchable);\n if (!searchable) {\n setResults(NO_RESULTS);\n setIsFetching(false);\n }\n }\n\n useEffect(() => {\n if (!searchable) return undefined;\n\n let cancelled = false;\n\n async function fetchResults() {\n setIsFetching(true);\n try {\n const params = new URLSearchParams({\n q: debouncedQuery,\n source,\n limit: '10',\n });\n if (tableIdsKey) params.set('tableIds', tableIdsKey);\n\n const response = await contentFetch(`${resolvedSearchEndpoint}?${params.toString()}`);\n if (!response.ok) {\n throw new Error(`Search request failed: ${response.status}`);\n }\n\n const json: unknown = await response.json();\n\n if (!cancelled) {\n // An envelope that is not a successful result set now clears the\n // dropdown instead of leaving it untouched. Previously a 200 that\n // carried `success: false` (or a non-array `data`) fell through\n // every branch, so the PREVIOUS query's matches stayed on screen\n // under the new query — the throw path already reset them.\n const rows = toDocSearchResults(json);\n setResults(rows ? mapDocSearchResults(rows) : NO_RESULTS);\n }\n } catch (error) {\n console.error('Doc search error:', error);\n if (!cancelled) {\n setResults(NO_RESULTS);\n }\n } finally {\n if (!cancelled) {\n setIsFetching(false);\n }\n }\n }\n\n // Never rejects — try/catch/finally, every write gated on `cancelled`.\n void fetchResults();\n\n return () => {\n cancelled = true;\n };\n }, [debouncedQuery, searchable, source, tableIdsKey, resolvedSearchEndpoint]);\n\n // Derived loading state — single source of truth for \"should the\n // dropdown show 'Loading...' instead of 'No results found'\":\n const isLoading = query.trim().length >= 2 && (query !== debouncedQuery || isFetching);\n\n // Track whether dropdown should stay open (external link opened in new tab).\n const [keepOpen, setKeepOpen] = useState(false);\n\n const handleResultSelect = useCallback(\n (\n result: SearchResult,\n modifiers?: {\n metaKey?: boolean;\n ctrlKey?: boolean;\n shiftKey?: boolean;\n altKey?: boolean;\n button?: number;\n },\n ) => {\n const action = resolveSearchResultAction(result, source, runtime?.navigation.mode, resolvedComposeContentUrl);\n // Modifier / non-primary mouse click → force new tab regardless of\n // same-tab/new-tab decision. The dropdown row is a `<div>`, not an\n // `<a target=\"_blank\">`, so the browser doesn't background-tab\n // natively on cmd-click. Honor it explicitly here for parity with\n // the anchor-based surfaces (cards, chips, related-content). Plain\n // Enter from the keyboard passes `modifiers === undefined`.\n const wantsNewTab =\n modifiers &&\n (modifiers.metaKey ||\n modifiers.ctrlKey ||\n modifiers.shiftKey ||\n modifiers.altKey ||\n (typeof modifiers.button === 'number' && modifiers.button !== 0));\n switch (action.kind) {\n case 'navigate-same-tab': {\n if (wantsNewTab) {\n setKeepOpen(true);\n window.open(action.href, '_blank', NEW_TAB_FEATURES);\n return;\n }\n // Same-origin click:\n // 1. If the href is under the current doc-tree's baseRoute AND\n // an `onInPageSwap` callback is wired AND returns true →\n // consider in-page swap handled.\n // 2. Otherwise → embed-shim `router.push()` (soft RSC nav on\n // Next.js hosts, window.location.assign on bare hosts).\n setKeepOpen(false);\n const path =\n baseRoute && action.href.startsWith(`${baseRoute}/`) ? action.href.slice(baseRoute.length + 1) : null;\n if (path && onInPageSwap?.(path)) return;\n router.push(stripSameOriginToPath(action.href));\n return;\n }\n case 'navigate-new-tab':\n // Cross-origin (e.g. clicking a flamingo.run release from\n // product-hub) — open in a new tab. Keep dropdown open so the\n // user can pick another result without re-searching.\n setKeepOpen(true);\n // Embed-mode short-circuit — the row was clicked while the panel is\n // hosted inside an embedding app, so absolutize against the\n // embedder-supplied content origin and honor its `openExternal`\n // override. Not the only path an embed host takes: a composed\n // RELATIVE href resolves to `navigate-same-tab` above and never\n // reaches `decideNewTab`, which is what lets an embedder keep its\n // own in-app routes in-app.\n if (runtime?.navigation.mode === 'embed') {\n const targetPlatform = (result.metadata?.targetPlatform as string | null | undefined) ?? null;\n resolveExternalNavigation({\n href: action.href,\n targetPlatform,\n runtime,\n }).open();\n return;\n }\n window.open(action.href, '_blank', NEW_TAB_FEATURES);\n return;\n case 'ask-ai':\n // Row is searchable-but-not-openable (cap_table positions,\n // financial-kpi snapshots, anything backed by\n // `resolveUrl: () => null`). Dispatch a CustomEvent that\n // GlobalAskAI listens for — opens chat + drills via\n // `entityIdFilter` (primary-key only, same as inline-card Ask).\n setKeepOpen(false);\n window.dispatchEvent(new CustomEvent('ask-ai:open-with-ref', { detail: action.detail }));\n return;\n case 'route':\n // Final fallback: legacy navigation by path. Hits when a row\n // has neither URL nor pk metadata — a mapper/API regression.\n setKeepOpen(false);\n onNavigate(action.path);\n return;\n case 'noop':\n return;\n }\n },\n [onNavigate, source, baseRoute, router, onInPageSwap, runtime, resolvedComposeContentUrl],\n );\n\n // Reset keepOpen when query changes. Adjusted while rendering, not from an\n // effect: the dropdown's open state is read from this in the same render the\n // keystroke produced, so an effect held it pinned open for one extra frame.\n const [keepOpenForQuery, setKeepOpenForQuery] = useState(query);\n if (keepOpenForQuery !== query) {\n setKeepOpenForQuery(query);\n setKeepOpen(false);\n }\n\n return {\n query,\n setQuery,\n results,\n isLoading,\n handleResultSelect,\n keepDropdownOpen: keepOpen,\n };\n}\n"]}
@@ -3,16 +3,16 @@
3
3
 
4
4
 
5
5
 
6
- var _chunkKK3XU6YJcjs = require('./chunk-KK3XU6YJ.cjs');
6
+ var _chunkNIHPN72Ncjs = require('./chunk-NIHPN72N.cjs');
7
7
 
8
8
 
9
9
 
10
10
 
11
- var _chunkEGUU3QCUcjs = require('./chunk-EGUU3QCU.cjs');
11
+ var _chunkNNRRRWCDcjs = require('./chunk-NNRRRWCD.cjs');
12
12
 
13
13
 
14
14
 
15
- var _chunkKWTV3KGYcjs = require('./chunk-KWTV3KGY.cjs');
15
+ var _chunkGDHQTVBPcjs = require('./chunk-GDHQTVBP.cjs');
16
16
 
17
17
 
18
18
 
@@ -22,7 +22,7 @@ var _chunkKWTV3KGYcjs = require('./chunk-KWTV3KGY.cjs');
22
22
 
23
23
 
24
24
 
25
- var _chunkEGYX55HTcjs = require('./chunk-EGYX55HT.cjs');
25
+ var _chunkHQKWJFFXcjs = require('./chunk-HQKWJFFX.cjs');
26
26
 
27
27
 
28
28
  var _chunkLEISUAHFcjs = require('./chunk-LEISUAHF.cjs');
@@ -326,8 +326,8 @@ function toDocContent(value, requestedPath) {
326
326
  }
327
327
  function useDocumentTree(config, initialPath) {
328
328
  const { structureEndpoint, contentEndpoint, baseRoute } = config;
329
- const folderIndexFile = _nullishCoalesce(config.folderIndexFile, () => ( _chunkEGYX55HTcjs.DEFAULT_FOLDER_INDEX_FILE));
330
- const cleanInitialPath = _chunkEGYX55HTcjs.stripFolderIndexFromPath.call(void 0, _optionalChain([initialPath, 'optionalAccess', _4 => _4.replace, 'call', _5 => _5(/\/$/, "")]) || "", folderIndexFile);
329
+ const folderIndexFile = _nullishCoalesce(config.folderIndexFile, () => ( _chunkHQKWJFFXcjs.DEFAULT_FOLDER_INDEX_FILE));
330
+ const cleanInitialPath = _chunkHQKWJFFXcjs.stripFolderIndexFromPath.call(void 0, _optionalChain([initialPath, 'optionalAccess', _4 => _4.replace, 'call', _5 => _5(/\/$/, "")]) || "", folderIndexFile);
331
331
  const [structure, setStructure] = _react.useState.call(void 0, []);
332
332
  const [selectedPath, setSelectedPath] = _react.useState.call(void 0, cleanInitialPath);
333
333
  const [content, setContent] = _react.useState.call(void 0, null);
@@ -351,12 +351,12 @@ function useDocumentTree(config, initialPath) {
351
351
  } else if (pathname.startsWith(`${normalizedBaseRoute}/`)) {
352
352
  pathFromUrl = pathname.substring(`${normalizedBaseRoute}/`.length);
353
353
  }
354
- pathFromUrl = _chunkEGYX55HTcjs.stripFolderIndexFromPath.call(void 0, pathFromUrl, folderIndexFile);
354
+ pathFromUrl = _chunkHQKWJFFXcjs.stripFolderIndexFromPath.call(void 0, pathFromUrl, folderIndexFile);
355
355
  if (pathFromUrl !== selectedPathRef.current) {
356
356
  setSelectedPath(pathFromUrl);
357
357
  if (pathFromUrl) {
358
358
  const parentPath = pathFromUrl.includes("/") ? pathFromUrl.substring(0, pathFromUrl.lastIndexOf("/")) : pathFromUrl;
359
- setExpandedNodes(new Set(_chunkEGYX55HTcjs.getDocAncestorNodeIds.call(void 0, parentPath)));
359
+ setExpandedNodes(new Set(_chunkHQKWJFFXcjs.getDocAncestorNodeIds.call(void 0, parentPath)));
360
360
  }
361
361
  setTimeout(() => {
362
362
  scrollToContent();
@@ -374,7 +374,7 @@ function useDocumentTree(config, initialPath) {
374
374
  setSelectedPath(cleanInitialPath);
375
375
  if (cleanInitialPath) {
376
376
  const parentPath = cleanInitialPath.includes("/") ? cleanInitialPath.substring(0, cleanInitialPath.lastIndexOf("/")) : cleanInitialPath;
377
- setExpandedNodes(new Set(_chunkEGYX55HTcjs.getDocAncestorNodeIds.call(void 0, parentPath)));
377
+ setExpandedNodes(new Set(_chunkHQKWJFFXcjs.getDocAncestorNodeIds.call(void 0, parentPath)));
378
378
  }
379
379
  setExternalNavCount((n) => n + 1);
380
380
  }
@@ -399,7 +399,7 @@ function useDocumentTree(config, initialPath) {
399
399
  if (cleanInitialPath) {
400
400
  const pathForExpansion = cleanInitialPath.includes(".") ? cleanInitialPath.substring(0, cleanInitialPath.lastIndexOf("/")) : cleanInitialPath;
401
401
  if (pathForExpansion) {
402
- setExpandedNodes(new Set(_chunkEGYX55HTcjs.getDocAncestorNodeIds.call(void 0, pathForExpansion)));
402
+ setExpandedNodes(new Set(_chunkHQKWJFFXcjs.getDocAncestorNodeIds.call(void 0, pathForExpansion)));
403
403
  }
404
404
  } else if (nodes.length > 0) {
405
405
  const hasRootReadme = nodes.some((node) => node.type === "file" && node.path === folderIndexFile);
@@ -439,7 +439,7 @@ function useDocumentTree(config, initialPath) {
439
439
  setContent(null);
440
440
  return;
441
441
  }
442
- const probe = _chunkEGYX55HTcjs.findDocNodeByPath.call(void 0, _chunkEGYX55HTcjs.stripFolderIndexFromPath.call(void 0, path, folderIndexFile), structure);
442
+ const probe = _chunkHQKWJFFXcjs.findDocNodeByPath.call(void 0, _chunkHQKWJFFXcjs.stripFolderIndexFromPath.call(void 0, path, folderIndexFile), structure);
443
443
  const probeIsNoReadmeFolder = !!probe && probe.type === "folder" && !probe.hasReadme;
444
444
  const preStructureFolderLike = structure.length === 0 && !path.endsWith(".md");
445
445
  if (probeIsNoReadmeFolder || preStructureFolderLike) {
@@ -496,7 +496,7 @@ function useDocumentTree(config, initialPath) {
496
496
  if (selectedPath === "") {
497
497
  pathToFetch = folderIndexFile;
498
498
  } else {
499
- const node = _chunkEGYX55HTcjs.findDocNodeByPath.call(void 0, selectedPath, structure);
499
+ const node = _chunkHQKWJFFXcjs.findDocNodeByPath.call(void 0, selectedPath, structure);
500
500
  if (node && node.type === "folder" && !node.hasReadme) {
501
501
  const firstDocPath = findFirstDocPath(node);
502
502
  if (!firstDocPath) {
@@ -537,16 +537,16 @@ function useDocumentTree(config, initialPath) {
537
537
  if (node.type === "folder") {
538
538
  setExpandedNodes((prev) => {
539
539
  if (prev.has(node.id)) {
540
- const ancestorIds = _chunkEGYX55HTcjs.getDocAncestorNodeIds.call(void 0, node.path);
540
+ const ancestorIds = _chunkHQKWJFFXcjs.getDocAncestorNodeIds.call(void 0, node.path);
541
541
  ancestorIds.pop();
542
542
  return new Set(ancestorIds);
543
543
  }
544
- return new Set(_chunkEGYX55HTcjs.getDocAncestorNodeIds.call(void 0, node.path));
544
+ return new Set(_chunkHQKWJFFXcjs.getDocAncestorNodeIds.call(void 0, node.path));
545
545
  });
546
546
  } else {
547
547
  const lastSlash = node.path.lastIndexOf("/");
548
548
  if (lastSlash > 0) {
549
- setExpandedNodes(new Set(_chunkEGYX55HTcjs.getDocAncestorNodeIds.call(void 0, node.path.substring(0, lastSlash))));
549
+ setExpandedNodes(new Set(_chunkHQKWJFFXcjs.getDocAncestorNodeIds.call(void 0, node.path.substring(0, lastSlash))));
550
550
  }
551
551
  }
552
552
  lastFetchedPath.current = null;
@@ -561,7 +561,7 @@ function useDocumentTree(config, initialPath) {
561
561
  const hashIndex = path.indexOf("#");
562
562
  const anchor = hashIndex !== -1 ? path.substring(hashIndex) : "";
563
563
  const cleanPath = path.replace(/\/$/, "").split("#")[0];
564
- const pathForSelection = anchor && _optionalChain([options, 'optionalAccess', _6 => _6.fromInternalLink]) && cleanPath === "" ? selectedPathRef.current : _chunkEGYX55HTcjs.stripFolderIndexFromPath.call(void 0, cleanPath, folderIndexFile);
564
+ const pathForSelection = anchor && _optionalChain([options, 'optionalAccess', _6 => _6.fromInternalLink]) && cleanPath === "" ? selectedPathRef.current : _chunkHQKWJFFXcjs.stripFolderIndexFromPath.call(void 0, cleanPath, folderIndexFile);
565
565
  if (anchor && _optionalChain([options, 'optionalAccess', _7 => _7.fromInternalLink]) && pathForSelection === selectedPathRef.current) {
566
566
  _chunkZI27ALYXcjs.navigateSamePageHash.call(void 0, anchor, { headerOffset: _chunkZI27ALYXcjs.HUB_HEADER_OFFSET_PX });
567
567
  return;
@@ -625,15 +625,15 @@ function useDocumentTree(config, initialPath) {
625
625
  return docNavigation.register({
626
626
  baseRoute: normalizedBaseRoute,
627
627
  findNodeByPath: (path) => {
628
- const clean = _chunkEGYX55HTcjs.stripFolderIndexFromPath.call(void 0, path.replace(/\/$/, "").split("#")[0], folderIndexFile);
629
- return _nullishCoalesce(_chunkEGYX55HTcjs.findDocNodeByPath.call(void 0, clean, structure), () => ( null));
628
+ const clean = _chunkHQKWJFFXcjs.stripFolderIndexFromPath.call(void 0, path.replace(/\/$/, "").split("#")[0], folderIndexFile);
629
+ return _nullishCoalesce(_chunkHQKWJFFXcjs.findDocNodeByPath.call(void 0, clean, structure), () => ( null));
630
630
  },
631
631
  selectNode
632
632
  });
633
633
  }, [docNavigation, normalizedBaseRoute, structure, selectNode, folderIndexFile]);
634
634
  const isEmptyFolder = _react.useMemo.call(void 0, () => {
635
635
  if (!selectedPath || structure.length === 0) return false;
636
- const node = _chunkEGYX55HTcjs.findDocNodeByPath.call(void 0, selectedPath, structure);
636
+ const node = _chunkHQKWJFFXcjs.findDocNodeByPath.call(void 0, selectedPath, structure);
637
637
  return !!node && node.type === "folder" && !node.hasReadme && !findFirstDocPath(node);
638
638
  }, [selectedPath, structure]);
639
639
  return {
@@ -769,7 +769,7 @@ function DocViewerContent({
769
769
  label: _nullishCoalesce(_optionalChain([backButton, 'optionalAccess', _16 => _16.label]), () => ( "Back to home")),
770
770
  onClick: () => router.push(_nullishCoalesce(_optionalChain([backButton, 'optionalAccess', _17 => _17.href]), () => ( "/")))
771
771
  };
772
- const docSearch = _chunkKWTV3KGYcjs.useDocSearch.call(void 0, {
772
+ const docSearch = _chunkGDHQTVBPcjs.useDocSearch.call(void 0, {
773
773
  source: chatSource,
774
774
  baseRoute,
775
775
  searchEndpoint,
@@ -793,7 +793,7 @@ function DocViewerContent({
793
793
  onResolveLink: resolveLink
794
794
  });
795
795
  }, [content, renderContent, navigateToDoc, sourceId, resolveLink]);
796
- const selectedNodeDocType = selectedPath && structure.length > 0 ? _optionalChain([_chunkEGYX55HTcjs.findDocNodeByPath.call(void 0, selectedPath, structure), 'optionalAccess', _18 => _18.documentType]) : void 0;
796
+ const selectedNodeDocType = selectedPath && structure.length > 0 ? _optionalChain([_chunkHQKWJFFXcjs.findDocNodeByPath.call(void 0, selectedPath, structure), 'optionalAccess', _18 => _18.documentType]) : void 0;
797
797
  const activeDocType = _nullishCoalesce(_optionalChain([content, 'optionalAccess', _19 => _19.documentType]), () => ( selectedNodeDocType));
798
798
  const isMarkdownContent = !activeDocType || activeDocType === "markdown";
799
799
  const showStickyNav = isMarkdownContent;
@@ -804,9 +804,9 @@ function DocViewerContent({
804
804
  const containerBgStyle = colorPalette.containerBackground !== "transparent" ? { backgroundColor: colorPalette.containerBackground } : {};
805
805
  const defaultEmptyText = structure.length > 0 ? "Select a document from the sidebar to view" : "No documents yet. Add content from the admin panel.";
806
806
  const resolvedEmptyText = emptyStateText || defaultEmptyText;
807
- const inner = /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { style: { ...bgStyle, ...containerBgStyle }, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkEGYX55HTcjs.PageLayout, { title, subtitle, titleSize: "h1", titleWrap: true, backButton: _nullishCoalesce(backCfg, () => ( void 0)), children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex w-full flex-col gap-10", children: [
807
+ const inner = /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { style: { ...bgStyle, ...containerBgStyle }, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkHQKWJFFXcjs.PageLayout, { title, subtitle, titleSize: "h1", titleWrap: true, backButton: _nullishCoalesce(backCfg, () => ( void 0)), children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex w-full flex-col gap-10", children: [
808
808
  showAIChat && /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
809
- _chunkKWTV3KGYcjs.DocSearchBar,
809
+ _chunkGDHQTVBPcjs.DocSearchBar,
810
810
  {
811
811
  placeholder: `Search ${_optionalChain([sidebarLabel, 'optionalAccess', _23 => _23.toLowerCase, 'call', _24 => _24()]) || "documents"}...`,
812
812
  query: docSearch.query,
@@ -825,9 +825,9 @@ function DocViewerContent({
825
825
  ] })
826
826
  ] }) }),
827
827
  !error && /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "flex flex-1 flex-col items-start gap-6 lg:flex-row lg:gap-10", children: [
828
- /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-full lg:w-[320px] lg:shrink-0", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "lg:sticky lg:top-20", children: isLoadingStructure ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkEGYX55HTcjs.CategorySidebarSkeleton, {}) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
828
+ /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "w-full lg:w-[320px] lg:shrink-0", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "lg:sticky lg:top-20", children: isLoadingStructure ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkHQKWJFFXcjs.CategorySidebarSkeleton, {}) : /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, _jsxruntime.Fragment, { children: [
829
829
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, PersistentMobileDropdown, { isLoading: false, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
830
- _chunkEGUU3QCUcjs.MobileNavigationDropdown,
830
+ _chunkNNRRRWCDcjs.MobileNavigationDropdown,
831
831
  {
832
832
  nodes: structure,
833
833
  selectedPath,
@@ -841,7 +841,7 @@ function DocViewerContent({
841
841
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, PersistentSidebar, { isLoading: false, children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "hidden lg:block", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "space-y-4", children: [
842
842
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "font-semibold text-ods-text-secondary text-h5", children: sidebarLabel }),
843
843
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
844
- _chunkEGUU3QCUcjs.MultiLevelNavigation,
844
+ _chunkNNRRRWCDcjs.MultiLevelNavigation,
845
845
  {
846
846
  nodes: structure,
847
847
  selectedPath,
@@ -880,7 +880,7 @@ function DocViewerContent({
880
880
  showStickyNav && content && stickyNavSections.length > 0 && !isLoadingContent && /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "hidden lg:block", children: /* @__PURE__ */ _jsxruntime.jsxs.call(void 0, "div", { className: "sticky top-24", children: [
881
881
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "h3", { className: "mb-4 font-semibold text-ods-text-secondary text-h5", children: "ON THIS PAGE" }),
882
882
  /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
883
- _chunkEGUU3QCUcjs.StickySectionNav,
883
+ _chunkNNRRRWCDcjs.StickySectionNav,
884
884
  {
885
885
  sections: stickyNavSections,
886
886
  activeSection,
@@ -895,7 +895,7 @@ function DocViewerContent({
895
895
  ) })
896
896
  ] })
897
897
  ] }) }) });
898
- return shell ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkEGYX55HTcjs.PageShell, { contentClassName: `${bgClass} ${className}`, children: inner }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: `page-shell-content ${bgClass} ${className}`.trim(), children: inner });
898
+ return shell ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkHQKWJFFXcjs.PageShell, { contentClassName: `${bgClass} ${className}`, children: inner }) : /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: `page-shell-content ${bgClass} ${className}`.trim(), children: inner });
899
899
  }
900
900
 
901
901
  // src/components/docs/skeletons.tsx
@@ -985,11 +985,11 @@ function EmbedSkeleton({ documentType } = {}) {
985
985
 
986
986
  var DEFAULT_TITLE = "Documents";
987
987
  var defaultFallbackRenderer = () => /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "div", { className: "py-16 text-center", children: /* @__PURE__ */ _jsxruntime.jsx.call(void 0, "p", { className: "text-ods-text-secondary", children: "Unsupported document type" }) });
988
- var defaultPdfRenderer = (content, handlers) => content.fileUrl ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKK3XU6YJcjs.PdfViewer, { src: content.fileUrl, fileName: content.fileName }) : defaultFallbackRenderer(content, handlers);
989
- var defaultGoogleSheetRenderer = (content, handlers) => content.externalUrl ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkKK3XU6YJcjs.GoogleSheetsViewer, { externalUrl: content.externalUrl, fileName: content.fileName }) : defaultFallbackRenderer(content, handlers);
990
- var defaultFigmaRenderer = (content, handlers) => content.externalUrl ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkEGYX55HTcjs.FigmaEmbed, { url: content.externalUrl, title: content.fileName, loading: "eager" }) : defaultFallbackRenderer(content, handlers);
988
+ var defaultPdfRenderer = (content, handlers) => content.fileUrl ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkNIHPN72Ncjs.PdfViewer, { src: content.fileUrl, fileName: content.fileName }) : defaultFallbackRenderer(content, handlers);
989
+ var defaultGoogleSheetRenderer = (content, handlers) => content.externalUrl ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkNIHPN72Ncjs.GoogleSheetsViewer, { externalUrl: content.externalUrl, fileName: content.fileName }) : defaultFallbackRenderer(content, handlers);
990
+ var defaultFigmaRenderer = (content, handlers) => content.externalUrl ? /* @__PURE__ */ _jsxruntime.jsx.call(void 0, _chunkHQKWJFFXcjs.FigmaEmbed, { url: content.externalUrl, title: content.fileName, loading: "eager" }) : defaultFallbackRenderer(content, handlers);
991
991
  var defaultFileRenderer = (content) => /* @__PURE__ */ _jsxruntime.jsx.call(void 0,
992
- _chunkKK3XU6YJcjs.FileDownloadCard,
992
+ _chunkNIHPN72Ncjs.FileDownloadCard,
993
993
  {
994
994
  fileName: content.fileName,
995
995
  mimeType: content.mimeType,
@@ -1052,4 +1052,4 @@ function DocsHubPage({
1052
1052
 
1053
1053
 
1054
1054
  exports.PersistentFilterControls = PersistentFilterControls; exports.PersistentSearchContainer = PersistentSearchContainer; exports.PersistentSidebar = PersistentSidebar; exports.PersistentMobileDropdown = PersistentMobileDropdown; exports.DocNavigationProvider = DocNavigationProvider; exports.useDocNavigation = useDocNavigation; exports.useDocumentTree = useDocumentTree; exports.useScrollSpy = useScrollSpy; exports.DocViewer = DocViewer; exports.MarkdownSkeleton = MarkdownSkeleton; exports.EmbedSkeleton = EmbedSkeleton; exports.DocsHubPage = DocsHubPage;
1055
- //# sourceMappingURL=chunk-ADANP3ZA.cjs.map
1055
+ //# sourceMappingURL=chunk-GOHHFX4O.cjs.map