@flamingo-stack/openframe-frontend-core 0.0.292 → 0.0.293

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 (155) hide show
  1. package/dist/{chunk-6FHO73AP.js → chunk-26PKDALD.js} +7 -79
  2. package/dist/chunk-26PKDALD.js.map +1 -0
  3. package/dist/{chunk-B2U6INNO.js → chunk-2U2M2TG2.js} +4 -4
  4. package/dist/{chunk-OXOTKEYY.cjs → chunk-4W7NYJ3B.cjs} +23 -23
  5. package/dist/{chunk-OXOTKEYY.cjs.map → chunk-4W7NYJ3B.cjs.map} +1 -1
  6. package/dist/{chunk-KBKZYJRI.cjs → chunk-5E2HOSSH.cjs} +66 -19
  7. package/dist/chunk-5E2HOSSH.cjs.map +1 -0
  8. package/dist/{chunk-PZZGDS5I.cjs → chunk-6G2INVGG.cjs} +24 -22
  9. package/dist/chunk-6G2INVGG.cjs.map +1 -0
  10. package/dist/{chunk-CUQH4SHH.js → chunk-6GCI7JOE.js} +2 -2
  11. package/dist/{chunk-N6ZM5PYZ.js → chunk-7RIYT7ZH.js} +49 -2
  12. package/dist/chunk-7RIYT7ZH.js.map +1 -0
  13. package/dist/{chunk-E2YXRSDG.js → chunk-BRNHX6C6.js} +15 -13
  14. package/dist/chunk-BRNHX6C6.js.map +1 -0
  15. package/dist/{chunk-5FK7X3EE.js → chunk-DOMJSNXW.js} +8 -7
  16. package/dist/chunk-DOMJSNXW.js.map +1 -0
  17. package/dist/{chunk-VK4B6UGU.js → chunk-E4XABBSU.js} +16 -8
  18. package/dist/{chunk-VK4B6UGU.js.map → chunk-E4XABBSU.js.map} +1 -1
  19. package/dist/{chunk-DUIWR7RQ.js → chunk-EJXHZX2E.js} +3 -3
  20. package/dist/{chunk-5PELVUFT.cjs → chunk-EYEW6PTA.cjs} +44 -36
  21. package/dist/chunk-EYEW6PTA.cjs.map +1 -0
  22. package/dist/{chunk-HTYUZXQP.js → chunk-FJDPUPXC.js} +5 -5
  23. package/dist/{chunk-SLP4KXP6.js → chunk-FQJK446R.js} +8 -2
  24. package/dist/chunk-FQJK446R.js.map +1 -0
  25. package/dist/{chunk-JC5RN7ZS.cjs → chunk-FT4FCV7L.cjs} +6 -6
  26. package/dist/{chunk-JC5RN7ZS.cjs.map → chunk-FT4FCV7L.cjs.map} +1 -1
  27. package/dist/{chunk-ZHNL2IPK.cjs → chunk-J54Z3OCR.cjs} +8 -2
  28. package/dist/chunk-J54Z3OCR.cjs.map +1 -0
  29. package/dist/{chunk-2NJ44RTT.cjs → chunk-JSOMFVEV.cjs} +30 -30
  30. package/dist/{chunk-2NJ44RTT.cjs.map → chunk-JSOMFVEV.cjs.map} +1 -1
  31. package/dist/{chunk-Z6BK4XHH.cjs → chunk-KXCRGTRN.cjs} +10 -82
  32. package/dist/chunk-KXCRGTRN.cjs.map +1 -0
  33. package/dist/{chunk-5KD3S25X.cjs → chunk-LFGGF7OT.cjs} +139 -2
  34. package/dist/chunk-LFGGF7OT.cjs.map +1 -0
  35. package/dist/{chunk-N45M3TK3.js → chunk-NSPOYUBH.js} +2 -2
  36. package/dist/{chunk-TYZEMPPH.js → chunk-OQ6X7ZOC.js} +138 -1
  37. package/dist/chunk-OQ6X7ZOC.js.map +1 -0
  38. package/dist/{chunk-MDLWEJAV.cjs → chunk-RJL6PIOK.cjs} +454 -453
  39. package/dist/chunk-RJL6PIOK.cjs.map +1 -0
  40. package/dist/{chunk-IXDTNQF4.js → chunk-SOJCR63T.js} +4 -4
  41. package/dist/{chunk-5R5OODNE.cjs → chunk-TYMUKFP2.cjs} +40 -40
  42. package/dist/{chunk-5R5OODNE.cjs.map → chunk-TYMUKFP2.cjs.map} +1 -1
  43. package/dist/{chunk-CDJOKNCS.cjs → chunk-VTY7S2QG.cjs} +25 -19
  44. package/dist/chunk-VTY7S2QG.cjs.map +1 -0
  45. package/dist/{chunk-FFP2A77V.cjs → chunk-X3TSMCKX.cjs} +12 -12
  46. package/dist/{chunk-FFP2A77V.cjs.map → chunk-X3TSMCKX.cjs.map} +1 -1
  47. package/dist/{chunk-C667P6LZ.js → chunk-YICTMMXP.js} +13 -7
  48. package/dist/{chunk-C667P6LZ.js.map → chunk-YICTMMXP.js.map} +1 -1
  49. package/dist/{chunk-2BMVBPC7.cjs → chunk-YIGPRLQY.cjs} +9 -9
  50. package/dist/{chunk-2BMVBPC7.cjs.map → chunk-YIGPRLQY.cjs.map} +1 -1
  51. package/dist/components/chat/entity-cards/roadmap-card.d.ts +7 -1
  52. package/dist/components/chat/entity-cards/roadmap-card.d.ts.map +1 -1
  53. package/dist/components/chat/index.cjs +7 -7
  54. package/dist/components/chat/index.js +6 -6
  55. package/dist/components/contact/index.cjs +8 -8
  56. package/dist/components/contact/index.js +7 -7
  57. package/dist/components/docs/index.cjs +6 -6
  58. package/dist/components/docs/index.js +5 -5
  59. package/dist/components/docs/use-document-tree.d.ts.map +1 -1
  60. package/dist/components/embeds/index.cjs +8 -8
  61. package/dist/components/embeds/index.js +7 -7
  62. package/dist/components/faq/faq-section.d.ts.map +1 -1
  63. package/dist/components/faq/index.cjs +8 -8
  64. package/dist/components/faq/index.js +7 -7
  65. package/dist/components/features/index.cjs +7 -7
  66. package/dist/components/features/index.js +6 -6
  67. package/dist/components/index.cjs +214 -193
  68. package/dist/components/index.cjs.map +1 -1
  69. package/dist/components/index.js +50 -29
  70. package/dist/components/index.js.map +1 -1
  71. package/dist/components/navigation/index.cjs +7 -7
  72. package/dist/components/navigation/index.js +6 -6
  73. package/dist/components/onboarding-guides/index.cjs +24 -24
  74. package/dist/components/onboarding-guides/index.js +4 -4
  75. package/dist/components/related-content/index.cjs +8 -8
  76. package/dist/components/related-content/index.js +7 -7
  77. package/dist/components/shared/delivery/delivery-lists.d.ts.map +1 -1
  78. package/dist/components/shared/delivery/delivery-row.d.ts +8 -1
  79. package/dist/components/shared/delivery/delivery-row.d.ts.map +1 -1
  80. package/dist/components/shared/delivery/delivery-table.d.ts.map +1 -1
  81. package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -1
  82. package/dist/components/shared/roadmap/roadmap-view.d.ts.map +1 -1
  83. package/dist/components/tickets/help-center-card.d.ts +7 -1
  84. package/dist/components/tickets/help-center-card.d.ts.map +1 -1
  85. package/dist/components/tickets/help-center-list.d.ts.map +1 -1
  86. package/dist/components/tickets/index.cjs +82 -73
  87. package/dist/components/tickets/index.cjs.map +1 -1
  88. package/dist/components/tickets/index.js +24 -15
  89. package/dist/components/tickets/index.js.map +1 -1
  90. package/dist/components/tickets/ticket-center.d.ts.map +1 -1
  91. package/dist/components/tickets/ticket-row.d.ts +6 -1
  92. package/dist/components/tickets/ticket-row.d.ts.map +1 -1
  93. package/dist/components/ui/index.cjs +7 -7
  94. package/dist/components/ui/index.js +6 -6
  95. package/dist/hooks/index.cjs +5 -3
  96. package/dist/hooks/index.cjs.map +1 -1
  97. package/dist/hooks/index.d.ts +1 -0
  98. package/dist/hooks/index.d.ts.map +1 -1
  99. package/dist/hooks/index.js +4 -2
  100. package/dist/hooks/use-scroll-to-hash.d.ts +17 -0
  101. package/dist/hooks/use-scroll-to-hash.d.ts.map +1 -0
  102. package/dist/index.cjs +19 -7
  103. package/dist/index.cjs.map +1 -1
  104. package/dist/index.js +19 -7
  105. package/dist/utils/dev-sections/dev-section-param-keys.d.ts +10 -0
  106. package/dist/utils/dev-sections/dev-section-param-keys.d.ts.map +1 -1
  107. package/dist/utils/index.cjs +71 -1
  108. package/dist/utils/index.cjs.map +1 -1
  109. package/dist/utils/index.d.ts +2 -1
  110. package/dist/utils/index.d.ts.map +1 -1
  111. package/dist/utils/index.js +67 -2
  112. package/dist/utils/index.js.map +1 -1
  113. package/dist/utils/same-page-hash-nav.d.ts +37 -0
  114. package/dist/utils/same-page-hash-nav.d.ts.map +1 -0
  115. package/dist/utils/source-icons.d.ts.map +1 -1
  116. package/package.json +1 -1
  117. package/src/components/chat/entity-cards/roadmap-card.tsx +8 -1
  118. package/src/components/docs/use-document-tree.ts +45 -3
  119. package/src/components/faq/faq-section.tsx +22 -9
  120. package/src/components/shared/delivery/delivery-lists.tsx +9 -0
  121. package/src/components/shared/delivery/delivery-row.tsx +15 -2
  122. package/src/components/shared/delivery/delivery-table.tsx +7 -1
  123. package/src/components/shared/roadmap/roadmap-grid.tsx +7 -0
  124. package/src/components/shared/roadmap/roadmap-view.tsx +11 -0
  125. package/src/components/tickets/help-center-card.tsx +9 -17
  126. package/src/components/tickets/help-center-list.tsx +13 -0
  127. package/src/components/tickets/ticket-center.tsx +2 -0
  128. package/src/components/tickets/ticket-row.tsx +7 -1
  129. package/src/hooks/index.ts +5 -0
  130. package/src/hooks/use-scroll-to-hash.ts +74 -0
  131. package/src/utils/.source-icons.md +1 -1
  132. package/src/utils/dev-sections/dev-section-param-keys.ts +14 -0
  133. package/src/utils/index.ts +17 -1
  134. package/src/utils/same-page-hash-nav.ts +115 -0
  135. package/src/utils/source-icons.ts +7 -1
  136. package/dist/chunk-5FK7X3EE.js.map +0 -1
  137. package/dist/chunk-5KD3S25X.cjs.map +0 -1
  138. package/dist/chunk-5PELVUFT.cjs.map +0 -1
  139. package/dist/chunk-6FHO73AP.js.map +0 -1
  140. package/dist/chunk-CDJOKNCS.cjs.map +0 -1
  141. package/dist/chunk-E2YXRSDG.js.map +0 -1
  142. package/dist/chunk-KBKZYJRI.cjs.map +0 -1
  143. package/dist/chunk-MDLWEJAV.cjs.map +0 -1
  144. package/dist/chunk-N6ZM5PYZ.js.map +0 -1
  145. package/dist/chunk-PZZGDS5I.cjs.map +0 -1
  146. package/dist/chunk-SLP4KXP6.js.map +0 -1
  147. package/dist/chunk-TYZEMPPH.js.map +0 -1
  148. package/dist/chunk-Z6BK4XHH.cjs.map +0 -1
  149. package/dist/chunk-ZHNL2IPK.cjs.map +0 -1
  150. /package/dist/{chunk-B2U6INNO.js.map → chunk-2U2M2TG2.js.map} +0 -0
  151. /package/dist/{chunk-CUQH4SHH.js.map → chunk-6GCI7JOE.js.map} +0 -0
  152. /package/dist/{chunk-DUIWR7RQ.js.map → chunk-EJXHZX2E.js.map} +0 -0
  153. /package/dist/{chunk-HTYUZXQP.js.map → chunk-FJDPUPXC.js.map} +0 -0
  154. /package/dist/{chunk-N45M3TK3.js.map → chunk-NSPOYUBH.js.map} +0 -0
  155. /package/dist/{chunk-IXDTNQF4.js.map → chunk-SOJCR63T.js.map} +0 -0
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/utils/ods-color-utils.ts","../src/components/chat/types/message.types.ts","../src/components/chat/utils/chat-attachment-markdown.ts","../src/utils/date-formatters.ts","../src/utils/release-cover.ts","../src/utils/release-badge.ts","../src/components/chat/utils/agent-status-message.ts","../src/components/chat/utils/clickup-task-type-utils.ts","../src/utils/extract-items.ts","../src/components/chat/utils/scroll-anchor.ts","../src/components/chat/utils/auto-continuation-directive.ts","../src/components/chat/utils/flatten-assistant-content.ts","../src/components/chat/utils/slash-dispatch-utils.ts","../src/components/chat/utils/external-app-urls.ts","../src/utils/common.ts","../src/utils/os-platforms.ts","../src/utils/validation-utils.ts","../src/utils/confidence-helpers.ts","../src/utils/dev-sections/dev-section-param-keys.ts","../src/utils/tool-utils.ts","../src/types/shell.types.ts","../src/utils/shell-utils.ts","../src/types/os.types.ts","../src/utils/os-utils.ts","../src/utils/country-phone-utils.ts","../src/utils/generic-domain-utils.ts","../src/utils/color-analysis.ts","../src/utils/date-utils.ts","../src/utils/sse-decision-frame.ts","../src/types/announcement.ts","../src/types/product-release.ts","../src/types/delivery.ts","../src/types/tmcg.ts","../src/utils/dev-sections/openframe-dev-sections.ts","../src/utils/scroll-into-view.ts","../src/utils/list-url.ts","../src/utils/faq-anchor.ts","../src/utils/content-ref-groups.ts","../src/utils/suggestion-url.ts","../src/utils/doc-tree-nav.ts","../src/utils/doc-path-utils.ts","../src/utils/tree-builder.ts","../src/utils/markdown-to-plain.ts","../src/utils/markdown-section-extractor.ts","../src/utils/embed-url-converters.ts","../src/utils/index.ts","../src/components/navigation/sticky-section-nav.tsx","../src/components/navigation/multi-level-navigation.tsx"],"sourcesContent":["/**\n * ODS Color Token Utility Functions\n * \n * Provides runtime utilities for working with ODS color tokens\n */\n\nimport { colorTokens as odsTokens } from './ods-color-tokens-stub';\n\nexport type Platform = 'openmsp' | 'openframe' | 'flamingo';\nexport type ColorCategory = 'open' | 'flamingo' | 'system' | 'attention';\nexport type ColorVariant = 'base' | 'hover' | 'active' | 'focus' | 'disabled';\n\n/**\n * Gets the raw ODS token value for a specific color\n */\nexport function getODSToken(\n category: ColorCategory,\n color: string,\n variant: ColorVariant = 'base'\n): string | undefined {\n const tokenKey = `${category}-${color}-${variant}`;\n return (odsTokens as any)[tokenKey];\n}\n\n/**\n * Gets the current platform's accent color\n */\nexport function getPlatformAccentColor(platform?: Platform): string {\n const currentPlatform = platform || getCurrentPlatform();\n \n const platformColors = {\n 'openmsp': 'var(--ods-open-yellow-base)', // CSS variable instead of hex\n 'openframe': 'var(--ods-open-yellow-base)', // CSS variable instead of hex\n 'flamingo': 'var(--ods-flamingo-pink-base)' // CSS variable instead of hex\n };\n \n return platformColors[currentPlatform];\n}\n\n/**\n * Gets the current platform from environment or DOM\n */\nexport function getCurrentPlatform(): Platform {\n // Server-side: use environment variable\n if (typeof window === 'undefined') {\n return (process.env.NEXT_PUBLIC_APP_TYPE as Platform) || 'openmsp';\n }\n \n // Client-side: check DOM attribute first, fallback to environment\n const domPlatform = document.documentElement.getAttribute('data-app-type');\n if (domPlatform) {\n return domPlatform as Platform;\n }\n \n return (process.env.NEXT_PUBLIC_APP_TYPE as Platform) || 'openmsp';\n}\n\n/**\n * Switches the platform theme by updating CSS custom properties\n */\nexport function switchPlatformTheme(platform: Platform): void {\n if (typeof window === 'undefined') return;\n \n const root = document.documentElement;\n root.setAttribute('data-app-type', platform);\n \n // Note: Cannot modify process.env at runtime in production\n // This would only work in development/test environments\n \n // Dispatch custom event for components to react to platform changes\n window.dispatchEvent(new CustomEvent('platformThemeChanged', {\n detail: { platform }\n }));\n}\n\n/**\n * Gets a semantic color value for the current platform\n */\nexport function getSemanticColor(semanticName: string, platform?: Platform): string | undefined {\n if (typeof window === 'undefined') return undefined;\n \n const currentPlatform = platform || getCurrentPlatform();\n \n // Switch platform temporarily to get the color\n const originalPlatform = getCurrentPlatform();\n if (currentPlatform !== originalPlatform) {\n switchPlatformTheme(currentPlatform);\n }\n \n const testElement = document.createElement('div');\n document.body.appendChild(testElement);\n \n const computedStyle = getComputedStyle(testElement);\n const colorValue = computedStyle.getPropertyValue(`--color-${semanticName}`);\n \n document.body.removeChild(testElement);\n \n // Restore original platform\n if (currentPlatform !== originalPlatform) {\n switchPlatformTheme(originalPlatform);\n }\n \n return colorValue.trim() || undefined;\n}\n\n/**\n * Converts an ODS token to its corresponding Tailwind class\n */\nexport function tokenToTailwindClass(\n tokenKey: string,\n type: 'bg' | 'text' | 'border' = 'bg'\n): string | undefined {\n // Map common tokens to Tailwind classes\n const tokenMappings: Record<string, string> = {\n // Accent colors\n 'accent-primary': 'accent',\n 'accent-hover': 'accent-hover',\n 'accent-active': 'accent-active',\n \n // Background colors\n 'bg': 'bg',\n 'bg-card': 'card',\n 'bg-hover': 'bg-hover',\n 'bg-active': 'bg-active',\n \n // Text colors\n 'text-primary': 'text-primary',\n 'text-secondary': 'text-secondary',\n 'text-muted': 'text-muted',\n 'text-disabled': 'text-disabled',\n 'text-on-accent': 'text-on-accent',\n 'text-on-dark': 'text-on-dark',\n \n // Border colors\n 'border-default': 'border',\n 'border-hover': 'border-hover',\n 'border-focus': 'border-focus',\n \n // Status colors\n 'success': 'success',\n 'error': 'error',\n 'warning': 'warning',\n 'info': 'info'\n };\n \n const mappedToken = tokenMappings[tokenKey];\n if (!mappedToken) return undefined;\n \n return `${type}-ods-${mappedToken}`;\n}\n\n/**\n * Gets all available ODS tokens for a category\n */\nexport function getTokensByCategory(category: ColorCategory): Record<string, string> {\n const tokens: Record<string, string> = {};\n \n Object.entries(odsTokens).forEach(([key, value]) => {\n if (key.startsWith(`${category}-`)) {\n tokens[key] = value as string;\n }\n });\n \n return tokens;\n}\n\n/**\n * Validates if a color token exists in the ODS system\n */\nexport function isValidODSToken(tokenKey: string): boolean {\n return tokenKey in odsTokens;\n}\n\n/**\n * Gets the platform configuration for theming\n */\nexport function getPlatformConfig(platform?: Platform) {\n const currentPlatform = platform || getCurrentPlatform();\n \n return {\n platform: currentPlatform,\n accentColor: getPlatformAccentColor(currentPlatform),\n isDarkTheme: currentPlatform !== 'flamingo',\n isLightTheme: currentPlatform === 'flamingo',\n brandName: {\n 'openmsp': 'OpenMSP',\n 'openframe': 'OpenFrame',\n 'flamingo': 'Flamingo'\n }[currentPlatform]\n };\n}\n\n/**\n * Generates CSS custom property declarations for a platform\n */\nexport function generatePlatformCSS(platform: Platform): string {\n const accentColor = getPlatformAccentColor(platform);\n const tokens = getTokensByCategory('system');\n \n let css = `[data-app-type=\"${platform}\"] {\\n`;\n css += ` --color-accent-primary: ${accentColor};\\n`;\n \n // Add platform-specific overrides\n if (platform === 'flamingo') {\n css += ` --color-bg: var(--ods-system-greys-white);\\n`;\n css += ` --color-text-primary: var(--ods-system-greys-background);\\n`;\n }\n \n if (platform === 'openframe') {\n css += ` --color-bg: var(--ods-system-greys-darker);\\n`;\n }\n \n css += `}\\n`;\n \n return css;\n}\n\n/**\n * Applies a color token as a CSS custom property\n */\nexport function applyColorToken(\n element: HTMLElement,\n property: string,\n tokenKey: string\n): void {\n const tokenValue = (odsTokens as any)[tokenKey];\n if (tokenValue) {\n element.style.setProperty(`--${property}`, tokenValue);\n }\n}\n\n/**\n * Creates a color interpolation between two ODS tokens\n */\nexport function interpolateColors(\n startToken: string,\n endToken: string,\n progress: number\n): string {\n const startColor = (odsTokens as any)[startToken];\n const endColor = (odsTokens as any)[endToken];\n \n if (!startColor || !endColor) {\n return startColor || endColor || '#000000';\n }\n \n // Simple hex color interpolation\n const hexToRgb = (hex: string) => {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n return result ? {\n r: parseInt(result[1], 16),\n g: parseInt(result[2], 16),\n b: parseInt(result[3], 16)\n } : { r: 0, g: 0, b: 0 };\n };\n \n const rgbToHex = (r: number, g: number, b: number) => {\n return \"#\" + ((1 << 24) + (Math.round(r) << 16) + (Math.round(g) << 8) + Math.round(b)).toString(16).slice(1);\n };\n \n const start = hexToRgb(startColor);\n const end = hexToRgb(endColor);\n \n const interpolated = {\n r: start.r + (end.r - start.r) * progress,\n g: start.g + (end.g - start.g) * progress,\n b: start.b + (end.b - start.b) * progress\n };\n \n return rgbToHex(interpolated.r, interpolated.g, interpolated.b);\n}\n\n/**\n * Hook for React components to use platform-aware colors\n */\nexport function usePlatformColors(platform?: Platform) {\n const currentPlatform = platform || getCurrentPlatform();\n const config = getPlatformConfig(currentPlatform);\n \n return {\n platform: currentPlatform,\n accentColor: config.accentColor,\n isDarkTheme: config.isDarkTheme,\n isLightTheme: config.isLightTheme,\n brandName: config.brandName,\n getToken: (category: ColorCategory, color: string, variant?: ColorVariant) =>\n getODSToken(category, color, variant),\n switchTheme: (newPlatform: Platform) => switchPlatformTheme(newPlatform),\n getSemanticColor: (semanticName: string) => getSemanticColor(semanticName, currentPlatform)\n };\n}\n\n/**\n * Picks a near-black or near-white text color with adequate contrast on `hex`.\n * Uses Rec. 601 luminance.\n */\nexport function getReadableTextColor(hex: string): string {\n const n = parseInt(hex.replace('#', ''), 16)\n const r = (n >> 16) & 0xff\n const g = (n >> 8) & 0xff\n const b = n & 0xff\n const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255\n return luminance > 0.5 ? '#212121' : '#fafafa'\n}\n\n// Hex <-> RGB <-> HSL. Hex is #rrggbb lowercase; hexToRgb returns null on invalid.\n\nexport const HEX_PATTERN = /^#[0-9a-fA-F]{6}$/;\n\nfunction clamp(n: number, min: number, max: number): number {\n return Math.max(min, Math.min(max, n));\n}\n\nexport function hexToRgb(hex: string): { r: number; g: number; b: number } | null {\n if (!HEX_PATTERN.test(hex)) return null;\n const n = Number.parseInt(hex.slice(1), 16);\n return { r: (n >> 16) & 0xff, g: (n >> 8) & 0xff, b: n & 0xff };\n}\n\nexport function rgbToHex(r: number, g: number, b: number): string {\n const to = (n: number) => clamp(Math.round(n), 0, 255).toString(16).padStart(2, '0');\n return `#${to(r)}${to(g)}${to(b)}`;\n}\n\n// Ticket-status interaction states. The design-system presets derive their\n// hover/active fills by darkening each RGB channel a fixed step (10 → hover,\n// 20 → active). Applying the same rule to user-picked custom colors keeps their\n// interaction states consistent with the presets without hardcoding a variant\n// per color. Channels floor at 0 via rgbToHex's clamp.\nconst HOVER_DARKEN_STEP = 10;\nconst ACTIVE_DARKEN_STEP = 20;\n\nfunction darkenByStep(hex: string, step: number): string {\n const rgb = hexToRgb(hex);\n if (!rgb) return hex;\n return rgbToHex(rgb.r - step, rgb.g - step, rgb.b - step);\n}\n\nexport function deriveHoverColor(hex: string): string {\n return darkenByStep(hex, HOVER_DARKEN_STEP);\n}\n\nexport function deriveActiveColor(hex: string): string {\n return darkenByStep(hex, ACTIVE_DARKEN_STEP);\n}\n\nexport function rgbToHsl(r: number, g: number, b: number): { h: number; s: number; l: number } {\n const rn = r / 255;\n const gn = g / 255;\n const bn = b / 255;\n const max = Math.max(rn, gn, bn);\n const min = Math.min(rn, gn, bn);\n const l = (max + min) / 2;\n if (max === min) return { h: 0, s: 0, l: Math.round(l * 100) };\n const d = max - min;\n const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);\n let h: number;\n if (max === rn) h = (gn - bn) / d + (gn < bn ? 6 : 0);\n else if (max === gn) h = (bn - rn) / d + 2;\n else h = (rn - gn) / d + 4;\n h *= 60;\n return { h: Math.round(h), s: Math.round(s * 100), l: Math.round(l * 100) };\n}\n\nexport function hslToRgb(h: number, s: number, l: number): { r: number; g: number; b: number } {\n const sn = clamp(s, 0, 100) / 100;\n const ln = clamp(l, 0, 100) / 100;\n const hn = ((h % 360) + 360) % 360;\n if (sn === 0) {\n const v = Math.round(ln * 255);\n return { r: v, g: v, b: v };\n }\n const q = ln < 0.5 ? ln * (1 + sn) : ln + sn - ln * sn;\n const p = 2 * ln - q;\n const hue2rgb = (t: number) => {\n let tn = t;\n if (tn < 0) tn += 1;\n if (tn > 1) tn -= 1;\n if (tn < 1 / 6) return p + (q - p) * 6 * tn;\n if (tn < 1 / 2) return q;\n if (tn < 2 / 3) return p + (q - p) * (2 / 3 - tn) * 6;\n return p;\n };\n const hk = hn / 360;\n return {\n r: Math.round(hue2rgb(hk + 1 / 3) * 255),\n g: Math.round(hue2rgb(hk) * 255),\n b: Math.round(hue2rgb(hk - 1 / 3) * 255),\n };\n}\n\nexport default {\n getODSToken,\n getPlatformAccentColor,\n getCurrentPlatform,\n switchPlatformTheme,\n getSemanticColor,\n tokenToTailwindClass,\n getTokensByCategory,\n isValidODSToken,\n getPlatformConfig,\n generatePlatformCSS,\n applyColorToken,\n interpolateColors,\n usePlatformColors\n};","/**\n * Message-related types\n * Contains all message structures, segments, and content types\n */\n\nimport type { AssistantType, AuthorType, ChatApprovalStatus, MessageOwner } from './chat.types'\n\n// ========== Message Type Definitions ==========\n\nexport const MESSAGE_TYPE = {\n TEXT: 'TEXT',\n THINKING: 'THINKING',\n EXECUTING_TOOL: 'EXECUTING_TOOL',\n EXECUTED_TOOL: 'EXECUTED_TOOL',\n APPROVAL_REQUEST: 'APPROVAL_REQUEST',\n APPROVAL_RESULT: 'APPROVAL_RESULT',\n ERROR: 'ERROR',\n MESSAGE_START: 'MESSAGE_START',\n MESSAGE_END: 'MESSAGE_END',\n MESSAGE_REQUEST: 'MESSAGE_REQUEST',\n AI_METADATA: 'AI_METADATA',\n TOKEN_USAGE: 'TOKEN_USAGE',\n CONTEXT_COMPACTION_START: 'CONTEXT_COMPACTION_START',\n CONTEXT_COMPACTION_END: 'CONTEXT_COMPACTION_END',\n DIRECT_MESSAGE: 'DIRECT_MESSAGE',\n SYSTEM: 'SYSTEM',\n DIALOG_CLOSED: 'DIALOG_CLOSED',\n} as const\n\nexport type MessageType = typeof MESSAGE_TYPE[keyof typeof MESSAGE_TYPE]\n\n// ========== Scroll Anchor (per-message render hint) ==========\n\n/** Per-message viewport-positioning hint sent on the per-turn metadata\n * leading frame at the START of every assistant response. The chat\n * message-list reads it to override the default `use-stick-to-bottom`\n * tail behaviour for a single message. Field is OPTIONAL — when omitted\n * (or set to `'bottom'`) the chat tails as today. Only `'top'` opts in\n * to the alternative behaviour (used by display-action answers whose\n * body is a long article and should be read top-down). */\nexport const SCROLL_ANCHOR = { TOP: 'top', BOTTOM: 'bottom' } as const\n\nexport type ScrollAnchor = typeof SCROLL_ANCHOR[keyof typeof SCROLL_ANCHOR]\n\n// ========== Tool Execution Types ==========\n\nexport interface ToolExecutionData {\n type: 'EXECUTING_TOOL' | 'EXECUTED_TOOL'\n integratedToolType: string\n toolFunction: string\n /** Backend-issued human-readable title (mirrors `PendingToolCallData.toolTitle`). */\n toolTitle?: string\n parameters?: Record<string, any>\n result?: string\n success?: boolean\n /**\n * Backend-issued id (matches `PendingToolCallData.toolExecutionRequestId`).\n * When present, lets the accumulator merge this execution event into the\n * matching approval batch row instead of emitting a standalone segment.\n */\n toolExecutionRequestId?: string\n}\n\n/**\n * Snapshot of an in-flight tool kept between the `EXECUTING_TOOL` and\n * `EXECUTED_TOOL` events. The backend only sends `toolTitle` on\n * `EXECUTING_TOOL`; carrying this state lets the accumulator restore it onto\n * the merged `EXECUTED_TOOL` segment instead of falling back to the raw\n * `toolFunction`.\n */\nexport interface ExecutingToolState {\n integratedToolType: string\n toolFunction: string\n /** Mirrors {@link ToolExecutionData.toolTitle}; absent on `EXECUTED_TOOL`. */\n toolTitle?: string\n parameters?: Record<string, any>\n}\n\n// ========== Approval Request Types ==========\n\nexport interface ApprovalRequestField {\n /** Short label — e.g. \"Subject\", \"Priority\". Rendered in a muted\n * caps style above the value. */\n label: string\n /** Free-text value. Wraps and line-breaks are preserved\n * (`whitespace-pre-wrap`). */\n value: string\n}\n\nexport interface ApprovalRequestData {\n command: string\n /** Structured field list — preferred over `explanation`. When set,\n * the approval card renders a vertical label/value stack with\n * proper spacing. Falls back to `explanation` (a single paragraph)\n * when omitted. Keep BOTH when you want hosts on older lib\n * versions to still see the prose; new hosts should send only\n * `fields`. */\n fields?: ApprovalRequestField[]\n explanation?: string\n icon?: React.ReactNode\n requestId?: string\n approvalRequestId?: string\n approvalType?: string\n}\n\nexport interface ApprovalResultData {\n approvalRequestId: string\n approved: boolean\n approvalType?: string\n /** Display name of the user who resolved the request; null/absent for system actions. */\n resolvedByName?: string | null\n}\n\n/**\n * Single tool call inside a batch approval request.\n * Mirrors backend PendingToolCallDto.\n */\nexport interface PendingToolCallData {\n toolExecutionRequestId: string\n toolName: string\n toolTitle?: string\n toolExplanation?: string\n toolType?: string\n requiresApproval: boolean\n approvalType?: string | null\n toolCallArguments?: Record<string, any> | null\n}\n\n/**\n * Per-tool execution state inside an approval batch.\n * Populated by EXECUTING_TOOL / EXECUTED_TOOL chunks that carry a\n * `toolExecutionRequestId` matching one of the batch's tool calls.\n */\nexport interface ApprovalBatchExecutionState {\n status: 'executing' | 'done'\n result?: string\n success?: boolean\n}\n\nexport interface ApprovalBatchData {\n approvalRequestId: string\n /** Highest approval type required across the batch (e.g. ADMIN beats CLIENT). */\n approvalType: string\n toolCalls: PendingToolCallData[]\n /**\n * Keyed by `PendingToolCallData.toolExecutionRequestId`. Absent before\n * approval; rows without an entry render as \"queued\" (loader) once the\n * batch itself is approved.\n */\n executions?: Record<string, ApprovalBatchExecutionState>\n}\n\n// ========== Message Segment Types ==========\n\nexport type TextSegment = {\n type: 'text'\n text: string\n}\n\nexport type ThinkingSegment = {\n type: 'thinking'\n text: string\n}\n\nexport type ToolExecutionSegment = {\n type: 'tool_execution'\n data: ToolExecutionData\n}\n\nexport type ApprovalRequestSegment = {\n type: 'approval_request'\n data: ApprovalRequestData & { approvalType?: string }\n status?: ChatApprovalStatus\n onApprove?: (requestId?: string) => void | Promise<void>\n onReject?: (requestId?: string) => void | Promise<void>\n}\n\nexport type ApprovalBatchSegment = {\n type: 'approval_batch'\n data: ApprovalBatchData\n status?: ChatApprovalStatus\n /** Display name of the user who resolved the request; set when the batch is resolved (null/absent for system actions). */\n resolvedByName?: string | null\n onApprove?: (requestId?: string) => void | Promise<void>\n onReject?: (requestId?: string) => void | Promise<void>\n}\n\nexport type ErrorSegment = {\n type: 'error'\n title: string\n details?: string\n}\n\nexport type ContextCompactionSegment = {\n type: 'context_compaction'\n status: 'started' | 'completed'\n summary?: string\n}\n\nexport type MessageSegment = TextSegment | ThinkingSegment | ToolExecutionSegment | ApprovalRequestSegment | ApprovalBatchSegment | ErrorSegment | ContextCompactionSegment\n\nexport type MessageContent = string | MessageSegment[]\n\n// ========== Message Data Types (from GraphQL/API) ==========\n\nexport interface MessageDataBase {\n type: MessageType\n}\n\nexport interface TextMessageData extends MessageDataBase {\n type: 'TEXT'\n text?: string\n}\n\nexport interface ThinkingMessageData extends MessageDataBase {\n type: 'THINKING'\n text?: string\n}\n\nexport interface ExecutingToolMessageData extends MessageDataBase {\n type: 'EXECUTING_TOOL'\n integratedToolType?: string\n toolFunction?: string\n /** Backend-issued human-readable title (wire field, mirrors `ChunkData.title`). */\n title?: string\n parameters?: Record<string, any>\n toolExecutionRequestId?: string\n}\n\nexport interface ExecutedToolMessageData extends MessageDataBase {\n type: 'EXECUTED_TOOL'\n integratedToolType?: string\n toolFunction?: string\n /** Backend-issued human-readable title (wire field, mirrors `ChunkData.title`). */\n title?: string\n parameters?: Record<string, any>\n result?: string\n success?: boolean\n toolExecutionRequestId?: string\n}\n\nexport interface ApprovalRequestMessageData extends MessageDataBase {\n type: 'APPROVAL_REQUEST'\n approvalRequestId?: string\n approvalType?: string\n command?: string\n explanation?: string\n /** Present when the approval is a batch of tool calls (new format). */\n toolCalls?: PendingToolCallData[]\n}\n\nexport interface ApprovalResultMessageData extends MessageDataBase {\n type: 'APPROVAL_RESULT'\n approvalRequestId?: string\n approved?: boolean\n approvalType?: string\n /** Display name of the user who resolved the request; null/absent for system actions. */\n resolvedByName?: string | null\n}\n\nexport interface ErrorMessageData extends MessageDataBase {\n type: 'ERROR'\n error?: string\n details?: string\n}\n\nexport interface AIMetadataMessageData extends MessageDataBase {\n type: 'AI_METADATA'\n modelName?: string\n providerName?: string\n provider?: string\n contextWindow?: number\n}\n\nexport interface TokenUsageData {\n inputTokensSize: number\n outputTokensSize: number\n totalTokensSize: number\n contextSize: number\n}\n\nexport interface SystemMessageData extends MessageDataBase {\n type: 'SYSTEM'\n text?: string\n}\n\nexport interface ContextCompactionStartMessageData extends MessageDataBase {\n type: 'CONTEXT_COMPACTION_START'\n}\n\nexport interface ContextCompactionEndMessageData extends MessageDataBase {\n type: 'CONTEXT_COMPACTION_END'\n summary?: string\n}\n\nexport type MessageData =\n | TextMessageData\n | ThinkingMessageData\n | ExecutingToolMessageData\n | ExecutedToolMessageData\n | ApprovalRequestMessageData\n | ApprovalResultMessageData\n | ErrorMessageData\n | AIMetadataMessageData\n | SystemMessageData\n | ContextCompactionStartMessageData\n | ContextCompactionEndMessageData\n\n// ========== Historical Message Types ==========\n\nexport interface HistoricalMessage {\n id: string\n dialogId?: string\n chatType?: string\n createdAt: string\n owner?: MessageOwner\n messageData?: MessageData | MessageData[]\n}\n\n// ========== Processed Message Types ==========\n\nexport interface ProcessedMessage {\n id: string\n role: 'user' | 'assistant' | 'error' // Limited to display roles\n content: MessageContent\n name?: string\n assistantType?: AssistantType\n authorType?: AuthorType\n timestamp: Date\n avatar?: string\n}\n\n// ========== Base Message Interface ==========\n\nimport type { ChatRef as MessageChatRef } from '../chat-ref.types'\nimport type { ChatContextItem } from './context-item.types'\n\nexport interface Message {\n id: string\n role: 'user' | 'assistant' | 'error' // Limited to display roles\n content: MessageContent\n name?: string\n assistantType?: AssistantType\n authorType?: AuthorType\n timestamp?: Date\n avatar?: string | null\n /** Highest CONTENT chunk streamSeq that composed this message. Stamped on\n * realtime synthetics so `mergeHistoryWithRealtime` can decide history\n * coverage per-message (see `MergeableChatMessage.streamSeq`). */\n streamSeq?: number\n /** Per-row metadata for inline entity-card rendering on this message\n * (v6.1 §B.2.6). Keyed by `<documentType>:<primaryKey>`. Optional —\n * user messages and legacy turns omit this field. The host's\n * `renderEntityCard` callback resolves keys to inline components. */\n chatRefs?: Record<string, MessageChatRef>\n /** Entity-context items attached to this (user) message via the composer's\n * context picker. When present the message bubble renders the context\n * chips beneath its text (Figma node 31:28709). Optional — omitted for\n * assistant messages and turns sent without context. */\n contextItems?: ChatContextItem[]\n /** Per-message viewport-positioning hint. OPTIONAL — when omitted (the\n * default for every LLM Q&A / browse / search / find / Discuss path)\n * the chat tails as today via `use-stick-to-bottom`. Only `'top'` opts\n * in to the alternative top-anchor behaviour (display-action answers\n * whose body is a long article). The server is the sole decision-\n * maker — set on the metadata leading frame. */\n scrollAnchor?: ScrollAnchor\n /** When true the message is part of the API conversation history (sent\n * to the LLM so it has context) but is NOT rendered in the chat UI.\n *\n * Used for \"synthetic continuation\" turns: when the user clicks Approve\n * on a tool proposal, the host auto-fires a follow-up `sendMessage`\n * with `hidden: true` carrying a directive like \"the user just\n * approved <tool>; ask follow-up questions per protocol\". The LLM's\n * response IS rendered (as a normal assistant message); only the\n * trigger prompt is suppressed so the chat reads naturally:\n *\n * user: \"open a ticket\"\n * assistant: preamble + approval card\n * [user clicks Approve]\n * assistant: \"Now to triage faster, can you share...\" ← auto-fires\n *\n * Without this flag the trigger prompt would surface as a confusing\n * bubble like \"(continue per protocol)\" between the approval card\n * and the AI's follow-up. */\n hidden?: boolean\n}","/**\n * Shared helper for the host-side markdown formatter ↔ server-side\n * parser pair. Single source of truth so the host-side build (chat\n * bubble render) and the server-side strip (LLM input cleanup) can\n * never drift.\n *\n * Lib-side replacement for the hub's `lib/utils/chat-attachment-markdown.ts`.\n * The hub file pulled its constants from `lib/config/chat-attachment-config`\n * + its `ChatAttachment` type from `lib/types/chat-attachment`. Both are\n * inlined here as lib-local constants/types — they're stable wire-format\n * contracts (the hub's view-route owns the prefix + token-param name; any\n * change is a coordinated migration).\n *\n * URL flavors at a glance:\n * - HOST-SIDE (user-bubble render): uses the runtime's\n * `attachmentViewUrlPrefix` (hub default = `/api/storage/view/chat-attachments/`).\n * Embedded apps override per their reverse-proxy topology.\n * - SERVER-SIDE (strip regex): uses the constant\n * `CHAT_ATTACHMENT_VIEW_URL_PREFIX` directly. The chat ROUTE always\n * runs on the hub, so it always strips on the hub default.\n * - ANTHROPIC image-blocks: uses the RAW Supabase signed URL — NOT the\n * proxy URL — because Anthropic's image fetcher doesn't reliably\n * follow 302 redirects.\n *\n * Security gates inside the formatter:\n * - Filename markdown chars escaped via `escapeMarkdownInline` so a\n * name like `screenshot](https://evil.com).png` cannot terminate\n * the markdown image early and embed an attacker-controlled URL.\n * - `<` and `>` escaped too — prevents CommonMark autolink expansion\n * for filenames containing URL-shaped text.\n */\n\n// ---------------------------------------------------------------------------\n// Lib-local constants + types (mirror the hub's chat-attachment-config /\n// chat-attachment.ts). These are wire-format contracts owned by the hub's\n// view route; embedders that want to override the prefix do so via\n// `ChatRuntime.endpoints.attachmentViewUrlPrefix`, not by changing these.\n// ---------------------------------------------------------------------------\n\n/** Hub-default prefix for the view-proxy URL. Embedders override via the\n * runtime. The server-side strip regex always uses THIS value (server\n * runs on the hub). */\nexport const CHAT_ATTACHMENT_VIEW_URL_PREFIX = '/api/storage/view/chat-attachments/'\n\n/** Query parameter name for the HMAC view token. */\nexport const CHAT_ATTACHMENT_VIEW_TOKEN_QUERY_PARAM = 't'\n\n/** Subset Anthropic's image content-block API supports. HEIC / video\n * attachments fall through to the text-marker form so the LLM still sees\n * a reference even when it can't visually parse the file. */\nexport const ANTHROPIC_SUPPORTED_IMAGE_MIME = [\n 'image/jpeg',\n 'image/png',\n 'image/webp',\n 'image/gif',\n] as const\n\n/**\n * Wire shape for a single chat attachment.\n *\n * `viewToken` is server-issued (HMAC-signed) and embedded in the markdown\n * URL's `?t=` query parameter. The client never generates it — the upload\n * route returns it alongside the storage path.\n */\nexport interface ChatAttachment {\n storagePath: string\n viewToken: string\n contentType: string\n fileName: string\n size: number\n}\n\n// ---------------------------------------------------------------------------\n// URL building\n// ---------------------------------------------------------------------------\n\n/**\n * Build the markdown view-URL for a chat attachment.\n *\n * `viewUrlPrefix` is parameterized (NOT pulled from the constant) so\n * host-side callers can pass the runtime's prefix\n * (`ChatRuntime.endpoints.attachmentViewUrlPrefix`) — embedded apps\n * supply an absolute URL against the hub's origin so chat-history\n * markdown works cross-origin.\n *\n * Server-side callers (the strip regex source) should pass\n * `CHAT_ATTACHMENT_VIEW_URL_PREFIX` directly.\n */\nexport function buildChatAttachmentViewUrl(\n viewUrlPrefix: string,\n storagePath: string,\n viewToken: string,\n): string {\n return `${viewUrlPrefix}${storagePath}?${CHAT_ATTACHMENT_VIEW_TOKEN_QUERY_PARAM}=${encodeURIComponent(viewToken)}`\n}\n\n// ---------------------------------------------------------------------------\n// Markdown formatter\n// ---------------------------------------------------------------------------\n\n/**\n * Escape inline-markdown special characters from a filename before\n * interpolation. Closes the markdown-injection / phishing primitive:\n * a filename like `screenshot](https://evil.com).png` would otherwise\n * terminate `![filename](...)` early and emit an attacker-controlled\n * URL in the user's bubble.\n *\n * Escapes: `[`, `]`, `(`, `)`, `\\`, `\\n`, `\\r`, `<`, `>`, `\"`.\n */\nexport function escapeMarkdownInline(text: string): string {\n return text.replace(/[[\\]()\\\\\\n\\r<>\"]/g, (ch) => {\n switch (ch) {\n case '\\n':\n return ' '\n case '\\r':\n return ''\n default:\n return `\\\\${ch}`\n }\n })\n}\n\n/**\n * Format a single chat-attachment as a markdown line to append to the\n * user's bubble text BEFORE sending.\n *\n * Images (MIME in `ANTHROPIC_SUPPORTED_IMAGE_MIME`) emit the `![]()`\n * image form so the browser renders the attachment inline. Everything\n * else (HEIC, video, audio) emits the `[Attached: ...]` link form so the\n * bubble shows a clickable file pill instead of a broken-image icon.\n */\nexport function formatChatAttachmentMarkdownForBubble(\n att: ChatAttachment,\n viewUrlPrefix: string,\n): string {\n const safeName = escapeMarkdownInline(att.fileName)\n const url = buildChatAttachmentViewUrl(viewUrlPrefix, att.storagePath, att.viewToken)\n const isImage = (ANTHROPIC_SUPPORTED_IMAGE_MIME as readonly string[]).includes(att.contentType)\n return isImage ? `\\n\\n![${safeName}](${url})` : `\\n\\n[Attached: ${safeName}](${url})`\n}\n\n// ---------------------------------------------------------------------------\n// Server-side strip regex + parser\n// ---------------------------------------------------------------------------\n\n/**\n * Module-private: the `CHAT_ATTACHMENT_VIEW_URL_PREFIX` with regex\n * meta-characters escaped, so it can be safely interpolated into\n * regex sources. Computed ONCE at module load. Exported so other\n * regex-building call sites can share the same source of truth.\n */\nexport const CHAT_ATTACHMENT_VIEW_URL_PREFIX_REGEX_ESCAPED =\n CHAT_ATTACHMENT_VIEW_URL_PREFIX.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/**\n * Single anchored regex matching both the image (`![]()`) and link\n * (`[Attached: ...]`) forms keyed on the server-side prefix.\n *\n * `gm` flags — multi-line anchored so the regex matches per-line.\n * Non-chat markdown links/images stay untouched because the regex\n * requires the literal `/api/storage/view/chat-attachments/` prefix.\n */\nexport const CHAT_ATTACHMENT_MARKDOWN_PATTERN = new RegExp(\n `^\\\\s*!?\\\\[[^\\\\]]*\\\\]\\\\(${CHAT_ATTACHMENT_VIEW_URL_PREFIX_REGEX_ESCAPED}[^)]+\\\\)\\\\s*$`,\n 'gm',\n)\n\n/**\n * Strip pre-embedded chat-attachment markdown lines from `text`.\n * Returns the cleaned text + the storage paths extracted from the\n * matched URLs.\n */\nexport function stripChatAttachmentMarkdown(text: string): {\n stripped: string\n storagePaths: string[]\n} {\n const storagePaths: string[] = []\n const pathExtract = new RegExp(\n `\\\\(${CHAT_ATTACHMENT_VIEW_URL_PREFIX_REGEX_ESCAPED}([^?)]+)`,\n )\n const stripped = text.replace(CHAT_ATTACHMENT_MARKDOWN_PATTERN, (match) => {\n const m = match.match(pathExtract)\n if (m && m[1]) storagePaths.push(m[1])\n return ''\n })\n return {\n stripped: stripped.replace(/\\n{3,}/g, '\\n\\n').trim(),\n storagePaths,\n }\n}\n","/**\n * Shared Date Formatting Utilities\n * Single source of truth for date formatting across the application\n */\n\nconst MONTHS_LONG = [\n 'January', 'February', 'March', 'April', 'May', 'June',\n 'July', 'August', 'September', 'October', 'November', 'December',\n] as const\n\nconst MONTHS_SHORT = [\n 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun',\n 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec',\n] as const\n\n/**\n * Split an ISO date / date-time string into `[year, month, day]` strings.\n * Internal — date-only inputs avoid the `new Date(...)` timezone shift\n * (which renders `\"2025-11-11\"` as Nov 10 west of UTC).\n */\nfunction splitYmd(dateString: string): [string, string, string] | null {\n const head = dateString.split('T')[0]\n const parts = head.split('-')\n if (parts.length !== 3) return null\n return [parts[0], parts[1], parts[2]]\n}\n\n/**\n * Format release date — avoids timezone shifts.\n * @returns e.g. `\"November 11, 2025\"`\n */\nexport function formatReleaseDate(dateString: string): string {\n const ymd = splitYmd(dateString)\n if (!ymd) return dateString\n const [year, month, day] = ymd\n return `${MONTHS_LONG[parseInt(month) - 1]} ${parseInt(day)}, ${year}`\n}\n\n/**\n * Short-form date — `\"Jan 5, 2025\"`. Same TZ-safe parsing as\n * `formatReleaseDate`; differs only in month abbreviation. Single source\n * of truth for the short-month-day-year shape used across hub admin\n * cards (waitlist, publication, media, investor, campaign, etc.) and\n * lib chat cards (campaign-card-admin).\n */\nexport function formatDateShort(dateString: string): string {\n const ymd = splitYmd(dateString)\n if (!ymd) return dateString\n const [year, month, day] = ymd\n return `${MONTHS_SHORT[parseInt(month) - 1]} ${parseInt(day)}, ${year}`\n}\n\n/**\n * Slash-form date — `\"11/11/2025\"`. TZ-safe (string-split, no\n * `new Date(...)`). Used by hub admin product-release + customer-\n * interview cards where the compact MM/DD/YYYY layout is desired.\n */\nexport function formatDateSlashUTC(dateString: string): string {\n const ymd = splitYmd(dateString)\n if (!ymd) return dateString\n const [year, month, day] = ymd\n return `${month}/${day}/${year}`\n}\n","/**\n * Single source of truth for the ProductReleaseCard cover-image fallback\n * priority. Every surface that renders a `<ProductReleaseCard>` (catalog row,\n * chat-inline dispatcher, chat wrapper) computes the cover via this helper —\n * adding a new fallback source or reordering the priority is ONE edit.\n *\n * **Priority** (intentional):\n * 1. `featured_image` — editor-curated cover, always wins.\n * 2. `highlight_video_thumbnail` — AI-generated highlight reel thumb.\n * 3. `main_video_thumbnail` — full-release video thumb.\n * 4. `og_image_url` — SEO fallback.\n * 5. `null` — caller renders the entity placeholder.\n *\n * **`hasVideoCover`** fires ONLY when the active cover is a video thumb\n * (priority 2 or 3). When the editor configured a still `featured_image`, a\n * Play overlay would be misleading and is suppressed.\n *\n * Operator note: `||` (not `??`) so empty-string DB values fall through to the\n * next fallback rather than rendering `<Image src=\"\">`.\n *\n * (Lifted from the hub so the rich `buildProductReleaseCardProps` works in the\n * lib / any embedder, not just the hub.)\n */\n\nexport interface ReleaseCoverSource {\n featured_image?: string | null\n highlight_video_thumbnail?: string | null\n main_video_thumbnail?: string | null\n og_image_url?: string | null\n}\n\nexport interface ReleaseCoverResult {\n cover: string | null\n hasVideoCover: boolean\n}\n\nexport function resolveReleaseCover(r: ReleaseCoverSource): ReleaseCoverResult {\n const cover =\n r.featured_image ||\n r.highlight_video_thumbnail ||\n r.main_video_thumbnail ||\n r.og_image_url ||\n null\n const hasVideoCover = !r.featured_image && !!(r.highlight_video_thumbnail || r.main_video_thumbnail)\n return { cover, hasVideoCover }\n}\n","/**\n * Single source of truth for mapping a product release's `release_type` to\n * the `StatusBadge` colorScheme used in the `ProductReleaseCard` lg variant's\n * metadata-grid Type cell. Every surface that renders a\n * `<ProductReleaseCard size=\"lg\" ...>` must pass this value as\n * `releaseTypeBadgeColor` or the card falls back to a `'—'` placeholder\n * (the card requires BOTH `releaseType && releaseTypeBadgeColor` to paint a\n * colored badge).\n *\n * Mapping mirrors the visual hierarchy operators expect at a glance:\n * major → error (red, breaking change)\n * minor → cyan (accent, feature)\n * patch → success (green, safe bump)\n * beta/alpha → warning (yellow, preview)\n *\n * (Lifted from the hub so every embedder — not just the hub — gets the rich\n * card metadata. The admin form's `releaseTypeOptions.color` array uses a\n * different color vocabulary — different concern; don't unify here.)\n */\nexport type ReleaseType = 'major' | 'minor' | 'patch' | 'beta' | 'alpha'\nexport type ReleaseTypeBadgeColor = 'error' | 'cyan' | 'success' | 'warning'\n\n/**\n * Accepts a wider input type than `ReleaseType` because list-API rows can\n * carry legacy / unenforced `release_type` values. Returns `undefined` for\n * unknown values — the card guards on `releaseType && releaseTypeBadgeColor`\n * to decide whether to render a colored badge or fall back to an em-dash, so\n * an undefined return is the correct failure mode (badge hidden), not a crash.\n */\nexport function releaseTypeToBadgeColor(\n t: ReleaseType | string | null | undefined,\n): ReleaseTypeBadgeColor | undefined {\n switch (t) {\n case 'major':\n return 'error'\n case 'minor':\n return 'cyan'\n case 'patch':\n return 'success'\n case 'beta':\n case 'alpha':\n return 'warning'\n default:\n return undefined\n }\n}\n","/**\n * Status color-scheme resolver used by chat-adjacent badges (StatusBadge\n * components rendering job, ClickUp task, and log-level statuses).\n *\n * Lib-side subset of the hub's `lib/utils/agent-status-message.ts`. The\n * hub file also carries CSS-class resolvers + an `AgentStatusMessage`\n * builder for the SSE-status surface; those live hub-side because they\n * reference hub-only ODS class names. This function returns the abstract\n * color scheme name only — the consuming `StatusBadge` (in this lib)\n * maps it to concrete colors.\n */\n\nexport type ColorScheme = 'success' | 'error' | 'warning' | 'cyan' | 'default'\n\n/**\n * Map a status to a color scheme name for StatusBadge components.\n * Supports job/processing statuses, ClickUp task statuses, and log levels.\n */\nexport function getStatusColorScheme(status: string): ColorScheme {\n const s = status?.toLowerCase() || ''\n\n // Exact matches first (most common)\n switch (status) {\n case 'completed':\n case 'success':\n case 'active': // review cycles, feature flags, etc.\n return 'success'\n case 'failed':\n case 'failure':\n case 'cancelled':\n case 'error':\n return 'error'\n case 'running':\n case 'processing':\n case 'closed': // review cycles past their active window\n return 'cyan'\n case 'pending':\n case 'warning':\n return 'warning'\n case 'draft': // review cycles not yet opened to reviewers\n case 'info':\n return 'default'\n }\n\n // Partial matches for ClickUp-style statuses (Complete, Done, Working, etc.)\n if (s.includes('complete') || s.includes('done')) return 'success'\n if (s.includes('review')) return 'cyan'\n if (s.includes('working') || s.includes('progress')) return 'warning'\n if (s.includes('blocked') || s.includes('failed')) return 'error'\n\n return 'default'\n}\n","/**\n * ClickUp custom_item_id → human-readable label resolver.\n *\n * Lib-side subset of the hub's `lib/utils/clickup-task-type-utils.ts`.\n * The hub file also exports delivery / workspace slug helpers used by\n * the sync engine; only the chat-surfaced label resolver migrates here\n * because chat-inline cards and activity feeds need it.\n *\n * Reference: ClickUp's standard task types (custom_item_id values):\n * null/1000=Task, 1001=Milestone, 1002=Recurring, 1003=Subtask,\n * 1004=Form, 1006=Plan, 1007=Strategy, 1008=Bug, 1009=Request,\n * 1010=Feature, 1011=Story, 1012=Epic, 1013=Component, 1014=Initiative.\n */\n\nexport const CUSTOM_ITEM_ID = {\n TASK: 1000,\n MILESTONE: 1001,\n RECURRING: 1002,\n SUBTASK: 1003,\n FORM: 1004,\n PLAN: 1006,\n STRATEGY: 1007,\n BUG: 1008,\n REQUEST: 1009,\n FEATURE: 1010,\n STORY: 1011,\n EPIC: 1012,\n COMPONENT: 1013,\n INITIATIVE: 1014,\n} as const\n\n/**\n * Display name for a ClickUp task type — used in chat-inline cards and\n * activity feeds where the type badge would otherwise show a numeric ID.\n *\n * Returning `null` for unknown IDs lets callers fall back to a generic\n * \"Task\" affordance rather than printing `task-1100` or similar.\n */\nexport function getTaskTypeLabel(\n customItemId: number | null | undefined,\n): string | null {\n switch (customItemId) {\n case CUSTOM_ITEM_ID.TASK: return 'Task'\n case CUSTOM_ITEM_ID.MILESTONE: return 'Milestone'\n case CUSTOM_ITEM_ID.RECURRING: return 'Recurring'\n case CUSTOM_ITEM_ID.SUBTASK: return 'Subtask'\n case CUSTOM_ITEM_ID.FORM: return 'Form'\n case CUSTOM_ITEM_ID.PLAN: return 'Plan'\n case CUSTOM_ITEM_ID.STRATEGY: return 'Strategy'\n case CUSTOM_ITEM_ID.BUG: return 'Bug'\n case CUSTOM_ITEM_ID.REQUEST: return 'Request'\n case CUSTOM_ITEM_ID.FEATURE: return 'Feature'\n case CUSTOM_ITEM_ID.STORY: return 'Story'\n case CUSTOM_ITEM_ID.EPIC: return 'Epic'\n case CUSTOM_ITEM_ID.COMPONENT: return 'Component'\n case CUSTOM_ITEM_ID.INITIATIVE: return 'Initiative'\n default: return null\n }\n}\n","/**\n * List-API response normalizers — HOISTED from\n * `components/chat/hooks/use-chat-card-item.ts` (private copies) so the\n * related-content rail can import them WITHOUT touching the chat hooks\n * chunk (which reaches `@tanstack/react-query`). Pure + server-safe.\n *\n * The hub's `lib/utils/entity-list-api.ts` re-exports these — one\n * implementation of response-shape normalization across both repos.\n */\n\n/** Extract the items array from each endpoint's response shape (some\n * return `{ items }`, others `{ posts }`, etc.). Single normalization\n * point — callers always read `Item[]`. */\nexport function extractItems(data: unknown): unknown[] {\n if (!data || typeof data !== 'object') return []\n const obj = data as Record<string, unknown>\n if (Array.isArray(obj.items)) return obj.items\n if (Array.isArray(obj.posts)) return obj.posts\n if (Array.isArray(obj.campaigns)) return obj.campaigns\n if (Array.isArray(obj.completed) || Array.isArray(obj.inProgress)) {\n // Delivery endpoint splits into completed/inProgress arrays — flatten.\n const completed = Array.isArray(obj.completed) ? obj.completed : []\n const inProgress = Array.isArray(obj.inProgress) ? obj.inProgress : []\n return [...completed, ...inProgress]\n }\n if (Array.isArray(obj.data)) return obj.data\n if (Array.isArray(data)) return data\n return []\n}\n\n/** Extract a stable id from a fetched item — different shapes use\n * `id` vs `external_id`. Used by the chat loader to match the fetched\n * array element back to the marker's id. */\nexport function extractItemId(type: string, item: unknown): string | null {\n if (!item || typeof item !== 'object') return null\n const obj = item as Record<string, unknown>\n if (type === 'roadmap_item' || type === 'delivery_item' || type === 'internal_task') {\n const ext = obj.external_id\n if (typeof ext === 'string') return ext\n }\n // RoadmapItem-shaped responses use `id` to carry the external_id; the\n // mapper renames external_id→id at the API boundary. Treat both id and\n // external_id as primary-key candidates for these clickup-backed types.\n const id = obj.id\n if (typeof id === 'string') return id\n if (typeof id === 'number') return String(id)\n return null\n}\n","/**\n * Wire glue + canonical const for the per-message `scrollAnchor`\n * render hint.\n *\n * The actual `SCROLL_ANCHOR` const + `ScrollAnchor` type lives in\n * `../types/message.types.ts` (it's part of the `Message` shape). This\n * module re-exports them alongside the wire-key + a `parseScrollAnchor`\n * narrower so server-side code that emits the metadata leading frame and\n * client-side code that consumes it agree on the literal value AND the\n * key on the wire frame.\n *\n * Consumed by:\n * - SERVER: emits the field into the metadata leading frame\n * (`...(scrollAnchor ? { [SCROLL_ANCHOR_WIRE_KEY]: x } : {})`).\n * - CLIENT: parses each leading frame and feeds the value through\n * `parseScrollAnchor` before storing it for the chat-message-list\n * to consume. Malformed wire values return `null` and are filtered\n * by metadata-merge logic — they never clobber a prior value.\n */\n\nimport { SCROLL_ANCHOR, type ScrollAnchor } from '../types/message.types'\n\nexport { SCROLL_ANCHOR, type ScrollAnchor }\n\n/** Wire-frame key for the `scrollAnchor` field on the metadata leading\n * frame. Used as a computed property name on both the emit side\n * (server) and the parse side (client) so a rename is one-file. */\nexport const SCROLL_ANCHOR_WIRE_KEY = \"scrollAnchor\" as const\n\n/** Narrow + validate a wire-supplied value. Returns the literal when\n * it matches one of the registry's known values; returns `null`\n * otherwise (which the metadata-merge null-skip filter then drops). */\nexport function parseScrollAnchor(raw: unknown): ScrollAnchor | null {\n return raw === SCROLL_ANCHOR.TOP || raw === SCROLL_ANCHOR.BOTTOM ? raw : null\n}\n","/**\n * Shared cross-process contract for the post-approval auto-continuation\n * directive. The client emits a hidden synthetic user message starting\n * with `AUTO_CONTINUATION_DIRECTIVE_PREFIX`; the server's `decideRoute`\n * matches that exact prefix on the RAW user query (pre-rewriter) and\n * hard-disables tools for the directive turn, forcing the LLM to\n * respond with prose follow-up questions instead of duplicating the\n * just-approved tool call.\n *\n * Single source of truth — both server-side `decideRoute` AND\n * client-side `autoContinueRef` import from here. Drifting the literal\n * on either side would silently break the duplicate-ticket guard\n * (server would route normally with tools wired; LLM would propose the\n * same tool again).\n *\n * `toolName` is typed as `string` (not a const-union) so the lib stays\n * generic across hub deployments — host code that has a sharper\n * `KnownWriteToolName` type can pass that through; this builder only\n * cares about a handful of string-equal branches.\n */\n\nexport const AUTO_CONTINUATION_DIRECTIVE_PREFIX = '[internal-auto-continuation]'\n\nexport interface BuildAutoContinuationOptions {\n ticketId?: string\n /** Reading of `args.status` on the just-approved tool call. Used to\n * branch the directive between \"post-create diagnostic Qs\",\n * \"post-close resolution ask\", and \"post-update acknowledgement\". */\n status?: string\n}\n\n/**\n * Build the directive text the client sends as a HIDDEN user message\n * after every successful Approve. The LLM treats it as the user\n * speaking; the chat shell filters it out at render time.\n *\n * Branch logic mirrors `TICKET_TOOL_PROTOCOL` §1a/§1b — keep them in\n * sync: the directive tells the model WHICH section to follow.\n */\nexport function buildAutoContinuationDirective(\n toolName: string,\n opts: BuildAutoContinuationOptions = {},\n): string {\n const ticketRef = opts.ticketId ? ` (ticket #${opts.ticketId})` : ''\n const ticketHash = opts.ticketId ? ` #${opts.ticketId}` : ''\n const isClose = toolName === 'update_ticket' && opts.status?.toUpperCase() === 'CLOSED'\n if (toolName === 'create_ticket') {\n return (\n `${AUTO_CONTINUATION_DIRECTIVE_PREFIX} The user just approved create_ticket${ticketRef}. ` +\n `Per ticket-protocol §1a, ask 2-4 SHORT, issue-specific diagnostic follow-up questions ` +\n `tailored to the symptom they reported in their original message. When they answer, ` +\n `propose an update_ticket with content_addendum carrying a clean Q&A digest. ` +\n `Do NOT call any tool in this turn — just write the questions as prose.`\n )\n }\n if (isClose) {\n return (\n `${AUTO_CONTINUATION_DIRECTIVE_PREFIX} The user just approved closing ticket${ticketHash}. ` +\n `Per ticket-protocol §1b, ask ONE short question: \"How was this resolved? I'll add it ` +\n `to the ticket as a closing note.\" Phrase kindly even if the user was curt. When they ` +\n `answer, propose an update_ticket with content_addendum=\"[Resolution] <their words>\". ` +\n `Do NOT call any tool in this turn — just write the prose ask.`\n )\n }\n return (\n `${AUTO_CONTINUATION_DIRECTIVE_PREFIX} The user just approved update_ticket${ticketHash}. ` +\n `Acknowledge the change in ONE short sentence; if anything else looks like it needs ` +\n `attention, ask. Do NOT call any tool in this turn.`\n )\n}\n","/**\n * `flattenAssistantContent` — coerce a chat message's `content` field\n * (string OR `MessageSegment[]`) into a plain string for wire transport.\n *\n * Background: useChat populates assistant messages with a structured\n * segment array as the streamed SSE arrives (text segments, approval\n * cards, tool_execution blocks, thinking blocks). The local React\n * state holds `content: MessageSegment[]` for those turns. When the\n * client serializes the conversation back to the server for the NEXT\n * turn, a naive `typeof content === 'string' ? content : ''` strip-\n * to-empty silently drops every post-approve receipt, every Sonnet-\n * streamed answer, and every diagnostic Q — Anthropic receives\n * `assistant: \"\"` for those turns and has no context to follow.\n *\n * The function:\n * 1. Returns plain strings as-is.\n * 2. For arrays, joins every `text`-typed segment's `text` field\n * with `\\n\\n` separators. Non-text segments (approval_request,\n * tool_execution, thinking) have no LLM-visible textual content\n * and are intentionally skipped — they shouldn't bloat the\n * request body.\n * 3. Returns `''` for anything else (null, undefined, unknown shape).\n */\nexport function flattenAssistantContent(raw: unknown): string {\n if (typeof raw === 'string') return raw\n if (!Array.isArray(raw)) return ''\n const parts: string[] = []\n for (const seg of raw) {\n if (seg && typeof seg === 'object' && (seg as { type?: string }).type === 'text') {\n const t = (seg as { text?: unknown }).text\n if (typeof t === 'string' && t.length > 0) parts.push(t)\n }\n }\n return parts.join('\\n\\n')\n}\n","/**\n * Lib-side portable subset of the hub's `lib/utils/slash-dispatch-utils.ts`.\n *\n * The full hub file owns the SERVER-SIDE parser/dispatcher (depends on\n * `slash-commands-config`, `slash-commands-static`, `doc-source-config-utils`,\n * `chat-admin-types`, `rag-table-config`) which stays hub-side.\n *\n * What MIGRATES here is the wire contract + the small text helpers consumed\n * by chat surfaces in the lib + the hub UI:\n * - `WireCommandOverride` — wire-shape (chat request body)\n * - `parseWireCommandOverride` — wire validator\n * - `CommandOverride` — server-built dispatch result type\n * (presentation typed as `string` here; the hub narrows via its own\n * `SlashCommandPresentation` union)\n * - `extractEntityIdFilter` — `CommandOverride` → narrowed filter\n * - `sanitizeTitleForChat` — collapse control chars in titles\n * - `formatSingularLookupInvocation` — `/cmd \"value\"` builder\n * - `buildDiscussAddendum` — server-side Discuss addendum prose\n *\n * Validation constants (regex + length caps) are duplicated here verbatim\n * to keep the lib's wire validator self-contained.\n */\n\nconst WIRE_TABLE_ID_REGEX = /^[a-z][a-z0-9-]*$/\nconst WIRE_ID_REGEX = /^[A-Za-z0-9._:/-]+$/\nconst WIRE_TABLE_ID_MAX = 60\nconst WIRE_ID_MAX = 200\nconst WIRE_QUERY_MAX = 2000\n\n/** Wire-supplied `CommandOverride` — the subset of fields a client (chat\n * shell) is allowed to send on the request body. Excludes\n * `systemPromptAddendum` by construction: addendum injection is a\n * Rule-5b bypass vector, so we never accept it from the wire. */\nexport interface WireCommandOverride {\n entityIdFilter?: { tableId: string; id: string }\n retrievalQueryOverride?: string\n}\n\n/**\n * Parse + validate a wire-supplied `commandOverride` from the chat\n * request body. Returns the validated subset OR `null` if the input is\n * missing / malformed in every field.\n */\nexport function parseWireCommandOverride(raw: unknown): WireCommandOverride | null {\n if (!raw || typeof raw !== 'object') return null\n const out: WireCommandOverride = {}\n const r = raw as Record<string, unknown>\n\n if (r.entityIdFilter && typeof r.entityIdFilter === 'object') {\n const f = r.entityIdFilter as Record<string, unknown>\n if (\n typeof f.tableId === 'string'\n && f.tableId.length > 0\n && f.tableId.length <= WIRE_TABLE_ID_MAX\n && WIRE_TABLE_ID_REGEX.test(f.tableId)\n && typeof f.id === 'string'\n && f.id.length > 0\n && f.id.length <= WIRE_ID_MAX\n && WIRE_ID_REGEX.test(f.id)\n ) {\n out.entityIdFilter = { tableId: f.tableId, id: f.id }\n }\n }\n if (\n typeof r.retrievalQueryOverride === 'string'\n && r.retrievalQueryOverride.length > 0\n && r.retrievalQueryOverride.length <= WIRE_QUERY_MAX\n ) {\n out.retrievalQueryOverride = r.retrievalQueryOverride\n }\n if (out.entityIdFilter === undefined && out.retrievalQueryOverride === undefined) {\n return null\n }\n return out\n}\n\n/**\n * Sanitize a free-text title for use inside a chat-visible string:\n * collapse control chars (\\r \\n \\t) to single spaces and trim. Defends\n * against a row title containing embedded newlines (which would split\n * the rendered prompt across visible lines + survive into ILIKE in a\n * confusing way) and tab/CR artifacts from sloppy imports.\n */\nexport function sanitizeTitleForChat(value: string | null | undefined): string {\n return (value ?? '').replace(/[\\r\\n\\t]+/g, ' ').trim()\n}\n\n/**\n * Compose a singular-lookup slash invocation as the user-visible chat\n * message: `/<cmd> \"<value>\"` (or `/<cmd>` when value is empty).\n *\n * SINGLE SOURCE OF TRUTH for the quoting convention `parseSlashCommand`\n * consumes: outer `\"`-pair triggers `singularLookup`; unquoted args run\n * FTS; empty args browse. Internal `\"` chars are backslash-escaped so\n * the visible string is paste-able / re-runnable.\n */\nexport function formatSingularLookupInvocation(cmdId: string, value?: string | null): string {\n // Escape `\\` BEFORE `\"` — otherwise a value ending in `\\` survives the\n // `\"`-escape pass and, when wrapped in `\"...\"`, the trailing `\\\"`\n // becomes a literal `\"` to a parser that processes backslash escapes,\n // breaking the close quote. CodeQL's `js/incomplete-sanitization` fires\n // on the previous one-pass `\\\"` escape for exactly this reason.\n const safe = sanitizeTitleForChat(value)\n .replace(/\\\\/g, '\\\\\\\\')\n .replace(/\"/g, '\\\\\"')\n return safe ? `/${cmdId} \"${safe}\"` : `/${cmdId}`\n}\n\n/** Command-override structure passed to `buildChatContext.opts.commandOverride`. */\nexport interface CommandOverride {\n retrievalScope?: string[]\n systemPromptAddendum?: string\n retrievalQueryOverride?: string | null\n /** Singular-item lookup. When set, retrieval bypasses FTS and queries the\n * named table directly via ILIKE on the matchFields. */\n entityLookup?: {\n tableId: string\n value: string\n matchFields: string[]\n }\n /** Singular row by primary key. Used by the inline object-card\n * \"Discuss\" action — the client knows the exact row id from a prior\n * chip. Higher precedence than `entityLookup` (route enforces) and\n * `retrievalQueryOverride`. The retrieval layer still runs the table's\n * `searchFilter` so privacy invariants hold. */\n entityIdFilter?: {\n tableId: string\n id: string\n }\n /** \"Browse\" bypass for bare slash list commands. The search layer\n * treats this as \"skip FTS — return rows from these tableIds ordered\n * by the configured `primaryDateColumn` DESC\". */\n browseScope?: string[]\n /** Presentation hint propagated from the slash command. Hub narrows to\n * its `SlashCommandPresentation` union; lib leaves as `string` for\n * decoupling. */\n presentation?: string\n}\n\n/**\n * Resolve the wire `entityIdFilter` from a `CommandOverride`, optionally\n * narrowed to a specific `tableId`. Returns the validated `{ tableId, id }`\n * pair only when both fields are present, non-empty, and (when an expected\n * tableId is passed) match. Returns `null` otherwise.\n */\nexport function extractEntityIdFilter(\n override: CommandOverride | null | undefined,\n expectedTableId?: string,\n): { tableId: string; id: string } | null {\n const f = override?.entityIdFilter\n if (!f) return null\n const tableId = typeof f.tableId === 'string' ? f.tableId : ''\n const id = typeof f.id === 'string' ? f.id : ''\n if (!tableId || !id) return null\n if (expectedTableId && tableId !== expectedTableId) return null\n return { tableId, id }\n}\n\n/**\n * Server-only synthesizer for the Discuss-action system-prompt addendum.\n * Called by the chat route AFTER `parseWireCommandOverride` accepts an\n * `entityIdFilter` — the addendum text is composed from server-trusted\n * values (the row's tableId + id) so a malicious client can't inject\n * arbitrary prompt content.\n */\nexport function buildDiscussAddendum(args: { tableId: string; id: string }): string {\n const idTag = args.id.length > 24 ? args.id.slice(0, 24) + '…' : args.id\n return (\n `Ask drill-in for row id=\"${args.id}\" in table \"${args.tableId}\". Focus the ` +\n `answer on this single record. If retrieval returns 0 rows (privacy ` +\n `filter), say so plainly per Rule 5b.\\n\\n` +\n `OPEN with the inline card on the FIRST line, immediately followed by ` +\n `the chip and a mono-font id tag — no prose before the card:\\n` +\n ` [card://<type>:${args.id}] [N] \\`${args.tableId} · ${idTag}\\`\\n` +\n `Use the EXACT <type> from the row's <document type=\"...\" id=\"${args.id}\"> tag.\\n\\n` +\n `SURFACE EVERY FIELD PRESENT in the retrieved record — the user already ` +\n `knows the title from the card; the value of Ask is the BODY (description, ` +\n `status, vote counts, dates, linked URLs, …). Walk each non-empty field. ` +\n `Don't invent fields, don't hallucinate values, silently omit absent fields.\\n\\n` +\n `FORMAT: card+chip+id-tag opener, then ONE framing sentence, then a compact ` +\n `bulleted \"**Field** — value\" list. Don't restate fields already in the list.`\n )\n}\n","/**\n * Unified URL builders for the third-party app deep-links used by chat\n * surfaces (chip clicks, inline-card tracking rows).\n *\n * Lib-side subset of the hub's `lib/utils/external-app-urls.ts`. Today\n * it carries the ClickUp deep-link helper only — HubSpot and admin\n * routes stay hub-side because they're consumed exclusively by\n * hub-internal mappers.\n */\n\nconst CLICKUP_APP_BASE = 'https://app.clickup.com'\n\n/** ClickUp task detail page in the ClickUp UI. Returns `null` when the\n * external id is missing so consumers don't end up with a\n * `.../t/undefined` link. */\nexport function clickupTaskUrl(externalId: string | null | undefined): string | null {\n if (!externalId) return null\n return `${CLICKUP_APP_BASE}/t/${externalId}`\n}\n","import { clsx, type ClassValue } from \"clsx\"\nimport { twMerge } from \"tailwind-merge\"\n\n/**\n * Utility functions for common operations\n */\n\n/**\n * Merge class names with Tailwind CSS\n * @param inputs - Class names to merge\n * @returns Merged class names\n */\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n\n/**\n * Delay execution for a specified time\n * @param ms - Milliseconds to delay\n * @returns Promise that resolves after the delay\n */\nexport function delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms))\n}\n\n/**\n * Generate a random string of specified length\n * @param length - Length of the string\n * @returns Random string\n */\nexport function generateRandomString(length = 8): string {\n const chars = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789\"\n let result = \"\"\n for (let i = 0; i < length; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length))\n }\n return result\n}\n\n/**\n * Truncate a string to a specified length\n * @param str - String to truncate\n * @param maxLength - Maximum length\n * @param suffix - Suffix to add to truncated string\n * @returns Truncated string\n */\nexport function truncateString(str: string, maxLength: number, suffix = \"...\"): string {\n if (str.length <= maxLength) return str\n return str.substring(0, maxLength - suffix.length) + suffix\n}\n\n/**\n * Serialize a JSON-LD schema for a `<script type=\"application/ld+json\">`\n * block — THE one home of the escape rule (the hub re-exports this from\n * lib/utils/breadcrumbs). JSON.stringify does NOT HTML-escape: a stored\n * \"</script>\" inside any admin/user-entered field would terminate the tag\n * early (stored XSS). Every \"<\" becomes the JSON escape sequence\n * backslash-u003c — still valid JSON, inert in HTML.\n */\nexport function serializeJsonLd(schema: unknown): string {\n return JSON.stringify(schema).replace(/</g, \"\\\\u003c\")\n}\n\n/**\n * Deep clone an object\n * @param obj - Object to clone\n * @returns Cloned object\n */\nexport function deepClone<T>(obj: T): T {\n return JSON.parse(JSON.stringify(obj))\n}\n\n/**\n * Get the Slack community join URL from environment variables\n * @returns Slack community join URL or fallback URL\n */\nexport function getSlackCommunityJoinUrl(): string {\n const url = process.env.NEXT_PUBLIC_SLACK_COMMUNITY_JOIN_URL\n if (!url) {\n console.warn('NEXT_PUBLIC_SLACK_COMMUNITY_JOIN_URL is not defined in environment variables')\n return 'https://join.slack.com/t/openmsp/shared_invite/zt-36bl7mx0h-3~U2nFH6nqHqoTPXMaHEHA'\n }\n return url\n}\n\n","import type { ComponentType } from 'react';\nimport { WindowsIcon, LinuxIcon, MacOSIcon } from '../components/icons';\n\n// OS-level platforms (Windows, Linux, MacOS)\n\nexport type OSPlatformId = 'windows' | 'linux' | 'darwin';\n\nexport interface OSPlatformOption {\n id: OSPlatformId;\n name: string;\n icon: ComponentType<any>;\n}\n\nexport const OS_PLATFORMS: OSPlatformOption[] = [\n { id: 'windows', name: 'Windows', icon: WindowsIcon },\n { id: 'linux', name: 'Linux', icon: LinuxIcon },\n { id: 'darwin', name: 'MacOS', icon: MacOSIcon }\n];\n\nexport const DEFAULT_OS_PLATFORM: OSPlatformId = 'windows';\n\n\n","/**\n * Email Domain Validation Utilities\n *\n * Validates email domain formats (the part after @ in email addresses)\n * Used for SSO domain allowlist feature to restrict auto-provisioning to specific domains.\n */\n\n/**\n * Validates an email domain format (the part after @ in email addresses)\n * Valid: example.com, sub.domain.org, my-company.co.uk\n * Invalid: http://example.com, @example.com, example, .com\n *\n * @param domain - The domain string to validate\n * @returns true if the domain is a valid email domain format\n */\nexport function isValidEmailDomain(domain: string): boolean {\n if (!domain || typeof domain !== 'string') {\n return false\n }\n\n const trimmed = domain.trim()\n if (!trimmed) {\n return false\n }\n\n // Domain regex: must have at least one dot, valid characters, and proper TLD\n // Allows subdomains, hyphens (not at start/end of labels), alphanumeric characters\n const domainRegex = /^(?!-)[A-Za-z0-9-]{1,63}(?<!-)(\\.[A-Za-z0-9-]{1,63})*\\.[A-Za-z]{2,}$/\n return domainRegex.test(trimmed)\n}\n\n/**\n * Validation result type\n */\nexport interface EmailDomainValidationResult {\n valid: boolean\n error?: string\n cleanedDomain?: string\n}\n\n/**\n * Validates an email domain with detailed error messages\n * Also cleans the input (removes leading @, trims whitespace)\n *\n * @param domain - The domain string to validate\n * @returns Validation result with error message if invalid\n */\nexport function validateEmailDomain(domain: string): EmailDomainValidationResult {\n if (!domain || typeof domain !== 'string') {\n return { valid: false, error: 'Email domain is required' }\n }\n\n // Clean the input: trim and remove leading @ if user enters it\n let trimmed = domain.trim()\n if (trimmed.startsWith('@')) {\n trimmed = trimmed.substring(1)\n }\n\n // Check for empty after cleaning\n if (!trimmed) {\n return { valid: false, error: 'Email domain is required' }\n }\n\n // Check for protocol (common mistake: entering full URL)\n if (trimmed.includes('://')) {\n return { valid: false, error: 'Enter email domain only (e.g., openframe.ai), not a URL' }\n }\n\n // Check for @ symbol (common mistake: entering full email address)\n if (trimmed.includes('@')) {\n return { valid: false, error: 'Enter domain only, not a full email address' }\n }\n\n // Check for spaces\n if (trimmed.includes(' ')) {\n return { valid: false, error: 'Domain cannot contain spaces' }\n }\n\n // Check for trailing slash\n if (trimmed.includes('/')) {\n return { valid: false, error: 'Enter domain only, without paths' }\n }\n\n // Validate the domain format\n if (!isValidEmailDomain(trimmed)) {\n return { valid: false, error: 'Invalid domain format (e.g., openframe.ai)' }\n }\n\n // Return success with the cleaned domain\n return { valid: true, cleanedDomain: trimmed.toLowerCase() }\n}\n\n/**\n * Validates a list of email domains\n * Returns the first error encountered or success if all domains are valid\n *\n * @param domains - Array of domain strings to validate\n * @returns Validation result with array of cleaned domains if valid\n */\nexport function validateEmailDomainList(domains: string[]): {\n valid: boolean\n error?: string\n cleanedDomains?: string[]\n} {\n if (!Array.isArray(domains)) {\n return { valid: false, error: 'Domains must be an array' }\n }\n\n const cleanedDomains: string[] = []\n const seenDomains = new Set<string>()\n\n for (const domain of domains) {\n const result = validateEmailDomain(domain)\n\n if (!result.valid) {\n return { valid: false, error: `Invalid domain \"${domain}\": ${result.error}` }\n }\n\n const cleanedDomain = result.cleanedDomain!\n\n // Check for duplicates (case-insensitive)\n if (seenDomains.has(cleanedDomain)) {\n return { valid: false, error: `Duplicate domain: ${cleanedDomain}` }\n }\n\n seenDomains.add(cleanedDomain)\n cleanedDomains.push(cleanedDomain)\n }\n\n return { valid: true, cleanedDomains }\n}\n\n/**\n * Cleans a domain string by removing common user input mistakes\n * Does NOT validate - use validateEmailDomain for validation\n *\n * @param domain - The domain string to clean\n * @returns Cleaned domain string\n */\nexport function cleanEmailDomain(domain: string): string {\n if (!domain || typeof domain !== 'string') {\n return ''\n }\n\n let cleaned = domain.trim().toLowerCase()\n\n // Remove leading @\n if (cleaned.startsWith('@')) {\n cleaned = cleaned.substring(1)\n }\n\n // Remove protocol if present\n cleaned = cleaned.replace(/^https?:\\/\\//, '')\n\n // Remove trailing slash and path\n cleaned = cleaned.split('/')[0]\n\n // Remove www. prefix\n cleaned = cleaned.replace(/^www\\./, '')\n\n return cleaned\n}\n","/**\n * Confidence helpers for AI enrichment\n * Shared utilities for displaying confidence levels from AI operations\n */\n\n/**\n * Get Tailwind CSS classes for confidence badge display (border and text color)\n * @param confidence - Confidence score (0-100)\n * @returns CSS classes for border and text color\n */\nexport function getConfidenceColorClass(confidence?: number): string {\n if (!confidence && confidence !== 0) return '';\n if (confidence >= 80) return 'border-green-500 text-green-500';\n if (confidence >= 50) return 'border-yellow-500 text-yellow-500';\n return 'border-red-500 text-red-500';\n}\n\n/**\n * Get confidence level category\n * @param confidence - Confidence score (0-100)\n * @returns Confidence level category\n */\nexport function getConfidenceLevel(confidence?: number): 'high' | 'medium' | 'low' | 'none' {\n if (!confidence && confidence !== 0) return 'none';\n if (confidence >= 80) return 'high';\n if (confidence >= 50) return 'medium';\n return 'low';\n}\n\n/**\n * Get Tailwind CSS class for border color only\n * @param confidence - Confidence score (0-100)\n * @returns CSS class for border color\n */\nexport function getConfidenceBorderClass(confidence?: number): string {\n if (!confidence && confidence !== 0) return '';\n if (confidence >= 80) return 'border-green-500';\n if (confidence >= 50) return 'border-yellow-500';\n return 'border-red-500';\n}\n\n/**\n * Get Tailwind CSS class for text color only\n * @param confidence - Confidence score (0-100)\n * @returns CSS class for text color\n */\nexport function getConfidenceTextClass(confidence?: number): string {\n if (!confidence && confidence !== 0) return '';\n if (confidence >= 80) return 'text-green-500';\n if (confidence >= 50) return 'text-yellow-500';\n return 'text-red-500';\n}\n\n/**\n * Get Tailwind CSS class for background color (subtle)\n * @param confidence - Confidence score (0-100)\n * @returns CSS class for subtle background color\n */\nexport function getConfidenceBgClass(confidence?: number): string {\n if (!confidence && confidence !== 0) return '';\n if (confidence >= 80) return 'bg-green-500/10';\n if (confidence >= 50) return 'bg-yellow-500/10';\n return 'bg-red-500/10';\n}\n\n/**\n * Get descriptive label for confidence level\n * @param confidence - Confidence score (0-100)\n * @returns Human-readable confidence label\n */\nexport function getConfidenceLabel(confidence?: number): string {\n if (!confidence && confidence !== 0) return 'Unknown';\n if (confidence >= 80) return 'High';\n if (confidence >= 50) return 'Medium';\n return 'Low';\n}\n","/**\n * Canonical URL param keys for the dev-center sections — the ONE source for both:\n * - the chrome registry (`OPENFRAME_DEV_SECTIONS`), which WRITES `?<key>=…` to the URL, and\n * - the list views (`RoadmapView` / `ProductReleasesView` / `DeliveryLists`), which READ\n * `?<key>=…` to fetch the filtered list.\n *\n * The chrome and the view MUST agree on the key or filtering silently breaks. Importing\n * from here (instead of re-declaring the literal in each place) makes that impossible to\n * get wrong. Pure string constants — no React, no heavy deps — so both server-bundled\n * utils and client views can import it freely.\n */\nexport const DEV_SECTION_PARAM_KEYS = {\n /** Free-text search box — shared by every dev-center section. */\n search: 'search',\n /** Roadmap (and Help Center tickets) status filter. */\n status: 'status',\n /** Product-releases stability-tier filter. */\n releaseStatus: 'release_status',\n /** Delivery (bug-fix / enhancement) task-type filter. */\n deliveryTaskType: 'task_type',\n} as const\n","/**\n * Tool Utilities\n *\n * Provides normalization and conversion utilities for tool types.\n * Handles various input formats (aliases, case variations) and converts\n * them to the canonical ToolType used throughout the platform.\n */\n\nimport { ToolType, toolLabels } from '../types/tool.types'\n\n/**\n * Map of common tool name variants to canonical ToolType\n */\nconst toolAliasMap: Record<string, ToolType> = {\n // Tactical RMM\n 'TACTICAL': 'TACTICAL_RMM',\n 'TACTICAL_RMM': 'TACTICAL_RMM',\n 'TACTICAL-RMM': 'TACTICAL_RMM',\n 'TACTICALRMM': 'TACTICAL_RMM',\n 'tactical': 'TACTICAL_RMM',\n 'tactical_rmm': 'TACTICAL_RMM',\n 'tactical-rmm': 'TACTICAL_RMM',\n 'tacticalrmm': 'TACTICAL_RMM',\n 'tacticalrmm-agent': 'TACTICAL_RMM',\n\n // Fleet MDM\n 'FLEET': 'FLEET_MDM',\n 'FLEET_MDM': 'FLEET_MDM',\n 'FLEET-MDM': 'FLEET_MDM',\n 'FLEETMDM': 'FLEET_MDM',\n 'fleet': 'FLEET_MDM',\n 'fleet_mdm': 'FLEET_MDM',\n 'fleet-mdm': 'FLEET_MDM',\n 'fleetmdm': 'FLEET_MDM',\n 'fleetmdm-agent': 'FLEET_MDM',\n\n // MeshCentral\n 'MESHCENTRAL': 'MESHCENTRAL',\n 'MESH': 'MESHCENTRAL',\n 'MESH_CENTRAL': 'MESHCENTRAL',\n 'MESH-CENTRAL': 'MESHCENTRAL',\n 'mesh': 'MESHCENTRAL',\n 'meshcentral': 'MESHCENTRAL',\n 'mesh_central': 'MESHCENTRAL',\n 'mesh-central': 'MESHCENTRAL',\n 'meshcentral-agent': 'MESHCENTRAL',\n\n // Authentik\n 'AUTHENTIK': 'AUTHENTIK',\n 'authentik': 'AUTHENTIK',\n\n // OpenFrame\n 'OPENFRAME': 'OPENFRAME',\n 'openframe': 'OPENFRAME',\n 'OPEN_FRAME': 'OPENFRAME',\n 'OPEN-FRAME': 'OPENFRAME',\n 'open_frame': 'OPENFRAME',\n 'open-frame': 'OPENFRAME',\n\n // OpenFrame Chat\n 'OPENFRAME_CHAT': 'OPENFRAME_CHAT',\n 'OPENFRAME-CHAT': 'OPENFRAME_CHAT',\n 'OPENFRAMECHAT': 'OPENFRAME_CHAT',\n 'openframe_chat': 'OPENFRAME_CHAT',\n 'openframe-chat': 'OPENFRAME_CHAT',\n 'openframechat': 'OPENFRAME_CHAT',\n\n // OpenFrame Client\n 'OPENFRAME_CLIENT': 'OPENFRAME_CLIENT',\n 'OPENFRAME-CLIENT': 'OPENFRAME_CLIENT',\n 'OPENFRAMECLIENT': 'OPENFRAME_CLIENT',\n 'openframe_client': 'OPENFRAME_CLIENT',\n 'openframe-client': 'OPENFRAME_CLIENT',\n 'openframeclient': 'OPENFRAME_CLIENT',\n\n // OSQUERY\n 'OSQUERY': 'OSQUERY',\n\n // System\n 'SYSTEM': 'SYSTEM',\n 'system': 'SYSTEM',\n}\n\n/**\n * Normalizes a tool name string to the canonical ToolType.\n * Handles various formats like 'tactical', 'TACTICAL_RMM', 'tactical-rmm', etc.\n *\n * @param input - The tool name string to normalize\n * @returns The canonical ToolType, or undefined if no match found\n *\n * @example\n * normalizeToolType('tactical') // => 'TACTICAL_RMM'\n * normalizeToolType('FLEET-MDM') // => 'FLEET_MDM'\n * normalizeToolType('unknown') // => undefined\n */\nexport function normalizeToolType(input?: string): ToolType | undefined {\n if (!input) return undefined\n\n // Try exact match first\n const exact = toolAliasMap[input]\n if (exact) return exact\n\n // Try uppercase\n const upper = input.toUpperCase()\n if (toolAliasMap[upper]) return toolAliasMap[upper]\n\n // Try lowercase\n const lower = input.toLowerCase()\n if (toolAliasMap[lower]) return toolAliasMap[lower]\n\n return undefined\n}\n\n/**\n * Normalizes a tool name string to the canonical ToolType with a fallback.\n * Returns 'SYSTEM' as the default if no match is found.\n *\n * @param input - The tool name string to normalize\n * @returns The canonical ToolType, defaults to 'SYSTEM' if no match\n *\n * @example\n * normalizeToolTypeWithFallback('tactical') // => 'TACTICAL_RMM'\n * normalizeToolTypeWithFallback('unknown') // => 'SYSTEM'\n */\nexport function normalizeToolTypeWithFallback(input?: string): ToolType {\n return normalizeToolType(input) ?? 'SYSTEM'\n}\n\n/**\n * Converts any tool name variant to its display label.\n * Handles normalization internally.\n *\n * @param input - The tool name string (any format)\n * @returns The display label, or the original input if no match\n *\n * @example\n * toToolLabel('tactical') // => 'Tactical'\n * toToolLabel('FLEET-MDM') // => 'Fleet'\n * toToolLabel('unknown') // => 'unknown'\n */\nexport function toToolLabel(input?: string): string {\n if (!input) return ''\n\n const toolType = normalizeToolType(input)\n if (toolType) {\n return toolLabels[toolType]\n }\n\n return input\n}\n\n/**\n * Checks if a string is a valid tool type (or can be normalized to one).\n *\n * @param input - The string to check\n * @returns True if the input can be normalized to a valid ToolType\n *\n * @example\n * isValidToolType('tactical') // => true\n * isValidToolType('FLEET_MDM') // => true\n * isValidToolType('unknown') // => false\n */\nexport function isValidToolType(input?: string): boolean {\n return normalizeToolType(input) !== undefined\n}\n\n/**\n * Gets all valid tool type aliases for a given canonical ToolType.\n * Useful for documentation or validation purposes.\n *\n * @param toolType - The canonical ToolType\n * @returns Array of all aliases that map to this tool type\n */\nexport function getToolTypeAliases(toolType: ToolType): string[] {\n return Object.entries(toolAliasMap)\n .filter(([_, value]) => value === toolType)\n .map(([key]) => key)\n}\n\n/**\n * Get display label for a tool type\n */\nexport function getToolLabel(toolType: ToolType): string {\n return toolLabels[toolType] || toolType\n}\n","/**\n * Centralized Shell Types\n *\n * Single source of truth for all shell/script type information across the platform.\n * Based on Tactical RMM supported shell types.\n */\nimport { PowershellLogoIcon } from '../components/icons-v2-generated/brand-logos/powershell-logo-icon'\nimport { PythonLogoIcon } from '../components/icons-v2-generated/brand-logos/python-logo-icon'\nimport {\n BashIcon,\n CmdIcon,\n DenoIcon,\n NushellIcon,\n ShellIcon\n} from '../components/icons'\nimport type { IconProps } from './icons'\n\n\n\nexport const ShellTypeValues = {\n POWERSHELL: 'POWERSHELL',\n CMD: 'CMD',\n BASH: 'BASH',\n PYTHON: 'PYTHON',\n NUSHELL: 'NUSHELL',\n DENO: 'DENO',\n SHELL: 'SHELL'\n} as const\n\nexport type ShellType = (typeof ShellTypeValues)[keyof typeof ShellTypeValues]\n\n/**\n * Shell type definition with all metadata\n */\nexport interface ShellTypeDefinition {\n id: ShellType\n label: string\n value: string\n icon: React.ComponentType<IconProps>\n}\n\n/**\n * Complete list of all shell types with icons and labels\n * SINGLE SOURCE OF TRUTH - Use this everywhere\n */\nexport const SHELL_TYPES: ShellTypeDefinition[] = [\n { id: ShellTypeValues.POWERSHELL, label: 'PowerShell', value: 'powershell', icon: PowershellLogoIcon },\n { id: ShellTypeValues.CMD, label: 'Batch', value: 'cmd', icon: CmdIcon },\n { id: ShellTypeValues.BASH, label: 'Bash', value: 'bash', icon: BashIcon },\n { id: ShellTypeValues.PYTHON, label: 'Python', value: 'python', icon: PythonLogoIcon },\n { id: ShellTypeValues.NUSHELL, label: 'Nu', value: 'nushell', icon: NushellIcon },\n { id: ShellTypeValues.DENO, label: 'Deno', value: 'deno', icon: DenoIcon },\n { id: ShellTypeValues.SHELL, label: 'Shell', value: 'shell', icon: ShellIcon },\n]\n\n/**\n * Maps shell types to display labels\n */\nexport const shellLabels: Record<ShellType, string> = {\n [ShellTypeValues.POWERSHELL]: 'PowerShell',\n [ShellTypeValues.CMD]: 'Batch',\n [ShellTypeValues.BASH]: 'Bash',\n [ShellTypeValues.PYTHON]: 'Python',\n [ShellTypeValues.NUSHELL]: 'Nu',\n [ShellTypeValues.DENO]: 'Deno',\n [ShellTypeValues.SHELL]: 'Shell'\n}\n","/**\n * Shell Type Utilities\n *\n * Helper functions for working with shell types.\n */\n\nimport React from 'react'\nimport { ShellType, SHELL_TYPES, shellLabels } from '../types/shell.types'\n\n/**\n * Get display label for a shell type\n */\nexport function getShellLabel(shellType?: string): string {\n if (!shellType) return 'Unknown'\n const normalized = shellType.toUpperCase() as ShellType\n return shellLabels[normalized] || shellType\n}\n\n/**\n * Get icon component for a shell type\n */\nexport function getShellIcon(shellType?: string): React.ComponentType<{ className?: string }> | undefined {\n if (!shellType) return undefined\n const normalized = shellType.toUpperCase() as ShellType\n const shellDef = SHELL_TYPES.find(s => s.id === normalized)\n return shellDef?.icon\n}\n","/**\n * Centralized OS Types\n *\n * Single source of truth for all operating system type information across the platform.\n * Handles normalization of OS values from various sources (Fleet MDM, Tactical RMM, GraphQL).\n */\n\nimport { AppleLogoIcon } from '../components/icons-v2-generated/brand-logos/apple-logo-icon'\nimport { LinuxLogoIcon } from '../components/icons-v2-generated/brand-logos/linux-logo-icon'\nimport { WindowsLogoGreyIcon } from '../components/icons-v2-generated/brand-logos/windows-logo-grey-icon'\nimport React from 'react'\nimport type { OSPlatformId } from '../utils/os-platforms'\n\nexport const OSTypeValues = {\n WINDOWS: 'WINDOWS',\n MACOS: 'MACOS',\n LINUX: 'LINUX'\n} as const\n\nexport type OSType = (typeof OSTypeValues)[keyof typeof OSTypeValues]\n\n/**\n * OS type definition with all metadata\n */\nexport interface OSTypeDefinition {\n id: OSType\n label: string\n value: string\n icon: React.ComponentType<any>\n platformId: OSPlatformId\n aliases: string[] // Alternative names/values that map to this OS\n}\n\n/**\n * Complete list of all OS types with icons and labels\n * SINGLE SOURCE OF TRUTH - Use this everywhere\n */\nexport const OS_TYPES: OSTypeDefinition[] = [\n {\n id: OSTypeValues.MACOS,\n label: 'macOS',\n value: OSTypeValues.MACOS,\n icon: AppleLogoIcon,\n platformId: 'darwin',\n aliases: ['darwin', 'macos', 'mac os', 'osx', 'os x', 'mac'] // Put more specific ones first, 'mac' last to avoid false matches\n },\n {\n id: OSTypeValues.WINDOWS,\n label: 'Windows',\n value: OSTypeValues.WINDOWS,\n icon: WindowsLogoGreyIcon,\n platformId: 'windows',\n aliases: ['windows', 'win32', 'win64', 'win'] // 'win' last since it's shortest\n },\n {\n id: OSTypeValues.LINUX,\n label: 'Linux',\n value: OSTypeValues.LINUX,\n icon: LinuxLogoIcon,\n platformId: 'linux',\n aliases: ['linux', 'ubuntu', 'debian', 'centos', 'redhat', 'fedora', 'pop', 'pop!_os', 'arch', 'manjaro']\n }\n]\n\n/**\n * Maps OS types to display labels\n */\nexport const osLabels: Record<OSType, string> = {\n [OSTypeValues.WINDOWS]: 'Windows',\n [OSTypeValues.MACOS]: 'macOS',\n [OSTypeValues.LINUX]: 'Linux'\n}\n","/**\n * OS Type Utilities\n *\n * Helper functions for working with OS types.\n */\n\nimport React from 'react'\nimport type { OSPlatformId } from './os-platforms'\nimport { OSType, OSTypeDefinition, OS_TYPES, osLabels } from '../types/os.types'\n\n/**\n * Normalize OS type string to standard OSType enum\n * Handles case-insensitive matching and various OS name variations\n *\n * @param osType - Raw OS type string from API or device data\n * @returns Normalized OSType or undefined if not recognized\n *\n * @example\n * normalizeOSType('windows') // 'WINDOWS'\n * normalizeOSType('Darwin') // 'MACOS'\n * normalizeOSType('Ubuntu') // 'LINUX'\n */\nexport function normalizeOSType(osType?: string): OSType | undefined {\n if (!osType) return undefined\n\n const normalized = osType.toLowerCase().trim()\n\n // Check for exact matches first, then partial matches\n // This prevents \"win\" from matching \"darwin\"\n for (const osTypeDef of OS_TYPES) {\n // Check for exact word match first\n if (osTypeDef.aliases.some(alias => {\n // Exact match\n if (normalized === alias) return true\n // Word boundary match (e.g., \"mac\" in \"mac os\" but not in \"vmac\")\n const wordBoundaryRegex = new RegExp(`\\\\b${alias}\\\\b`, 'i')\n return wordBoundaryRegex.test(osType)\n })) {\n return osTypeDef.id\n }\n }\n\n // Fallback to partial matching for version strings like \"macOS 26.0.1\"\n for (const osTypeDef of OS_TYPES) {\n if (osTypeDef.aliases.some(alias => normalized.includes(alias))) {\n return osTypeDef.id\n }\n }\n\n return undefined\n}\n\n/**\n * Get display label for an OS type\n *\n * @param osType - OS type string (can be raw or normalized)\n * @returns Display label\n */\nexport function getOSLabel(osType?: string): string {\n if (!osType) return 'Unknown'\n\n const normalized = normalizeOSType(osType)\n return normalized ? osLabels[normalized] : osType\n}\n\n/**\n * Get icon component for an OS type\n *\n * @param osType - OS type string (can be raw or normalized)\n * @returns Icon component or undefined\n */\nexport function getOSIcon(osType?: string): React.ComponentType<any> | undefined {\n if (!osType) return undefined\n\n const normalized = normalizeOSType(osType)\n if (!normalized) return undefined\n\n const osTypeDef = OS_TYPES.find(t => t.id === normalized)\n return osTypeDef?.icon\n}\n\n/**\n * Get OS type definition by OS type string\n *\n * @param osType - OS type string (can be raw or normalized)\n * @returns Complete OS type definition or undefined\n */\nexport function getOSTypeDefinition(osType?: string): OSTypeDefinition | undefined {\n if (!osType) return undefined\n\n const normalized = normalizeOSType(osType)\n if (!normalized) return undefined\n\n return OS_TYPES.find(t => t.id === normalized)\n}\n\n/**\n * Get platform ID for an OS type (for cross-referencing with OS_PLATFORMS)\n *\n * @param osType - OS type string (can be raw or normalized)\n * @returns Platform ID or undefined\n */\nexport function getOSPlatformId(osType?: string): OSPlatformId | undefined {\n const osTypeDef = getOSTypeDefinition(osType)\n return osTypeDef?.platformId\n}\n\n/**\n * Check if device OS matches a specific platform\n *\n * @param deviceOS - Device OS string\n * @param targetPlatform - Target platform to check against\n * @returns True if OS matches platform\n *\n * @example\n * isOSPlatform('Darwin', 'darwin') // true\n * isOSPlatform('Windows 10', 'windows') // true\n * isOSPlatform('Ubuntu', 'linux') // true\n */\nexport function isOSPlatform(deviceOS?: string, targetPlatform?: OSPlatformId): boolean {\n if (!deviceOS || !targetPlatform) return false\n\n const platformId = getOSPlatformId(deviceOS)\n return platformId === targetPlatform\n}\n","import { getCountries, getCountryCallingCode, isValidPhoneNumber, parsePhoneNumber } from 'libphonenumber-js'\nimport type { CountryCode } from 'libphonenumber-js'\n\nexport interface CountryPhoneData {\n code: CountryCode\n name: string\n dialCode: string\n flag: string\n}\n\n/**\n * Priority country codes shown at the top of country selectors\n */\nconst PRIORITY_CODES: CountryCode[] = ['US', 'CA', 'GB', 'AU']\n\n/**\n * Convert ISO 3166-1 alpha-2 country code to flag emoji\n */\nfunction countryCodeToFlag(code: string): string {\n return code\n .toUpperCase()\n .split('')\n .map(char => String.fromCodePoint(0x1F1E6 + char.charCodeAt(0) - 65))\n .join('')\n}\n\n/**\n * Build country phone data from libphonenumber-js metadata + Intl.DisplayNames\n */\nfunction buildCountryData(): { priority: CountryPhoneData[]; others: CountryPhoneData[] } {\n const displayNames = new Intl.DisplayNames(['en'], { type: 'region' })\n const allCodes = getCountries()\n\n const toData = (code: CountryCode): CountryPhoneData => ({\n code,\n name: displayNames.of(code) || code,\n dialCode: `+${getCountryCallingCode(code)}`,\n flag: countryCodeToFlag(code),\n })\n\n const priority = PRIORITY_CODES.map(toData)\n\n const prioritySet = new Set(PRIORITY_CODES)\n const others = allCodes\n .filter(c => !prioritySet.has(c))\n .map(toData)\n .sort((a, b) => a.name.localeCompare(b.name))\n\n return { priority, others }\n}\n\nlet _cache: { priority: CountryPhoneData[]; others: CountryPhoneData[] } | null = null\n\n/**\n * Get all countries split into priority (US, CA, GB, AU) and the rest alphabetically\n */\nexport function getCountryPhoneData() {\n if (!_cache) {\n _cache = buildCountryData()\n }\n return _cache\n}\n\n/**\n * Find country data by ISO code\n */\nexport function getCountryByCode(code: CountryCode): CountryPhoneData | undefined {\n const { priority, others } = getCountryPhoneData()\n return priority.find(c => c.code === code) || others.find(c => c.code === code)\n}\n\n/**\n * Validate a phone number for a specific country using libphonenumber\n */\nexport function validatePhoneNumber(phoneNumber: string, countryCode: CountryCode): boolean {\n if (!phoneNumber.trim()) return true // optional field\n return isValidPhoneNumber(phoneNumber, countryCode)\n}\n\n/**\n * Format a phone number to E.164 format (e.g., +14155552671)\n */\nexport function formatPhoneE164(phoneNumber: string, countryCode: CountryCode): string {\n try {\n const parsed = parsePhoneNumber(phoneNumber, countryCode)\n if (parsed && parsed.isValid()) {\n return parsed.format('E.164')\n }\n } catch {\n // fall through to manual formatting\n }\n const dialCode = `+${getCountryCallingCode(countryCode)}`\n const digits = phoneNumber.replace(/\\D/g, '')\n return `${dialCode}${digits}`\n}\n","/**\n * Generic Domain Detection\n * Detects personal/generic email providers (gmail, yahoo, etc.)\n */\n\nexport const GENERIC_EMAIL_DOMAINS = [\n // Major providers\n 'gmail.com',\n 'yahoo.com',\n 'hotmail.com',\n 'outlook.com',\n 'live.com',\n 'msn.com',\n // Apple\n 'icloud.com',\n 'me.com',\n 'mac.com',\n // Other providers\n 'aol.com',\n 'protonmail.com',\n 'mail.com',\n 'yandex.com',\n 'zoho.com',\n 'gmx.com',\n 'fastmail.com',\n // ISP-based\n 'comcast.net',\n 'verizon.net',\n 'att.net',\n 'sbcglobal.net',\n 'bellsouth.net',\n 'cox.net',\n // International\n 'qq.com',\n '163.com',\n '126.com',\n 'web.de',\n 't-online.de',\n] as const\n\nexport type GenericEmailDomain = (typeof GENERIC_EMAIL_DOMAINS)[number]\n\nexport function extractDomainFromEmail(email: string): string | null {\n if (!email || !email.includes('@')) return null\n return email.split('@')[1]?.toLowerCase() || null\n}\n\nexport function normalizeDomain(domain: string): string {\n return (\n domain\n ?.toLowerCase()\n .replace(/^https?:\\/\\//, '')\n .replace(/^www\\./, '')\n .split('/')[0]\n .trim() || ''\n )\n}\n\nexport function isGenericDomain(domain: string): boolean {\n const normalized = normalizeDomain(domain)\n return GENERIC_EMAIL_DOMAINS.includes(normalized as GenericEmailDomain)\n}\n\nexport function hasGenericEmailDomain(email: string): boolean {\n const domain = extractDomainFromEmail(email)\n return domain ? isGenericDomain(domain) : false\n}\n\nexport function isGenericWebsiteDomain(website: string): boolean {\n return isGenericDomain(normalizeDomain(website))\n}\n","// Color analysis utilities for dynamic badge colors\n\nexport interface ColorPalette {\n name: string;\n hex: string;\n rgb: [number, number, number];\n}\n\n// Your design system color palette - Star colors only\nexport const DESIGN_PALETTE: ColorPalette[] = [\n { name: 'yellow', hex: '#FFC008', rgb: [255, 192, 8] },\n { name: 'black', hex: '#161616', rgb: [22, 22, 22] },\n { name: 'gray', hex: '#888888', rgb: [136, 136, 136] },\n];\n\n/**\n * Convert hex color to RGB\n */\nexport function hexToRgb(hex: string): [number, number, number] {\n const result = /^#?([a-f\\d]{2})([a-f\\d]{2})([a-f\\d]{2})$/i.exec(hex);\n return result\n ? [parseInt(result[1], 16), parseInt(result[2], 16), parseInt(result[3], 16)]\n : [0, 0, 0];\n}\n\n/**\n * Calculate relative luminance for contrast calculations\n */\nfunction getLuminance(rgb: [number, number, number]): number {\n const [r, g, b] = rgb.map(c => {\n c = c / 255;\n return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);\n });\n return 0.2126 * r + 0.7152 * g + 0.0722 * b;\n}\n\n/**\n * Calculate contrast ratio between two colors\n */\nexport function getContrastRatio(color1: [number, number, number], color2: [number, number, number]): number {\n const lum1 = getLuminance(color1);\n const lum2 = getLuminance(color2);\n const brightest = Math.max(lum1, lum2);\n const darkest = Math.min(lum1, lum2);\n return (brightest + 0.05) / (darkest + 0.05);\n}\n\n/**\n * Extract dominant color from image canvas\n */\nexport function extractDominantColor(canvas: HTMLCanvasElement): [number, number, number] {\n const ctx = canvas.getContext('2d');\n if (!ctx) return [128, 128, 128]; // fallback gray\n\n const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n const data = imageData.data;\n\n // Sample pixels in a grid pattern for performance\n const sampleSize = Math.max(1, Math.floor(data.length / (4 * 1000))); // ~1000 samples\n const colorCounts: { [key: string]: number } = {};\n\n for (let i = 0; i < data.length; i += 4 * sampleSize) {\n const r = data[i];\n const g = data[i + 1];\n const b = data[i + 2];\n const alpha = data[i + 3];\n\n // Skip transparent pixels\n if (alpha < 128) continue;\n\n // Bucket colors to reduce noise (round to nearest 32)\n const bucketR = Math.round(r / 32) * 32;\n const bucketG = Math.round(g / 32) * 32;\n const bucketB = Math.round(b / 32) * 32;\n\n const key = `${bucketR},${bucketG},${bucketB}`;\n colorCounts[key] = (colorCounts[key] || 0) + 1;\n }\n\n // Find most common color\n let maxCount = 0;\n let dominantColor: [number, number, number] = [128, 128, 128];\n\n for (const [colorKey, count] of Object.entries(colorCounts)) {\n if (count > maxCount) {\n maxCount = count;\n const [r, g, b] = colorKey.split(',').map(Number);\n dominantColor = [r, g, b];\n }\n }\n\n return dominantColor;\n}\n\n/**\n * Find the best contrasting color from the design palette\n */\nexport function getBestContrastColor(imageColor: [number, number, number]): ColorPalette {\n let bestColor = DESIGN_PALETTE[0];\n let bestContrast = 0;\n\n for (const color of DESIGN_PALETTE) {\n const contrast = getContrastRatio(imageColor, color.rgb);\n if (contrast > bestContrast) {\n bestContrast = contrast;\n bestColor = color;\n }\n }\n\n // Ensure minimum contrast ratio of 3:1 for readability\n return bestContrast >= 3 ? bestColor : DESIGN_PALETTE.find(c => c.name === 'black') || bestColor;\n}\n\n/**\n * Load image and analyze its dominant color\n */\nexport function analyzeImageColor(imageSrc: string): Promise<ColorPalette> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n img.crossOrigin = 'anonymous';\n\n img.onload = () => {\n try {\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d');\n\n if (!ctx) {\n reject(new Error('Could not get canvas context'));\n return;\n }\n\n // Resize to smaller canvas for performance\n const maxSize = 100;\n const scale = Math.min(maxSize / img.width, maxSize / img.height);\n canvas.width = img.width * scale;\n canvas.height = img.height * scale;\n\n ctx.drawImage(img, 0, 0, canvas.width, canvas.height);\n\n const dominantColor = extractDominantColor(canvas);\n const bestContrastColor = getBestContrastColor(dominantColor);\n\n resolve(bestContrastColor);\n } catch (error) {\n reject(error);\n }\n };\n\n img.onerror = () => reject(new Error('Failed to load image'));\n img.src = imageSrc;\n });\n}\n\n/**\n * Extract the dominant edge/corner color from an image URL.\n * Used for background fill behind images in emails and cards.\n * Client-side only (uses canvas). Returns hex color string.\n */\nexport async function extractImageEdgeColorAsync(imageUrl: string): Promise<string> {\n return new Promise((resolve) => {\n if (typeof window === 'undefined') { resolve('#000000'); return }\n const img = new Image()\n img.crossOrigin = 'anonymous'\n img.onload = () => {\n try {\n const canvas = document.createElement('canvas')\n const ctx = canvas.getContext('2d')\n if (!ctx) { resolve('#000000'); return }\n const maxSize = 50\n const scale = Math.min(maxSize / img.naturalWidth, maxSize / img.naturalHeight)\n const w = Math.round(img.naturalWidth * scale)\n const h = Math.round(img.naturalHeight * scale)\n canvas.width = w; canvas.height = h\n ctx.drawImage(img, 0, 0, w, h)\n const data = ctx.getImageData(0, 0, w, h).data\n // Sample edge pixels (15% band on all sides) with color bucketing\n const edgeW = Math.max(2, Math.round(w * 0.15))\n const edgeH = Math.max(2, Math.round(h * 0.15))\n const bucketSize = 32\n const buckets = new Map<string, { r: number; g: number; b: number; count: number }>()\n for (let y = 0; y < h; y++) {\n for (let x = 0; x < w; x++) {\n if (!(x < edgeW || x >= w - edgeW || y < edgeH || y >= h - edgeH)) continue\n const i = (y * w + x) * 4\n if (data[i + 3] < 128) continue\n const br = Math.floor(data[i] / bucketSize) * bucketSize\n const bg = Math.floor(data[i + 1] / bucketSize) * bucketSize\n const bb = Math.floor(data[i + 2] / bucketSize) * bucketSize\n const key = `${br},${bg},${bb}`\n const existing = buckets.get(key)\n if (existing) { existing.r += data[i]; existing.g += data[i + 1]; existing.b += data[i + 2]; existing.count++ }\n else { buckets.set(key, { r: data[i], g: data[i + 1], b: data[i + 2], count: 1 }) }\n }\n }\n let best = { r: 0, g: 0, b: 0, count: 0 }\n for (const b of buckets.values()) { if (b.count > best.count) best = b }\n if (best.count === 0) { resolve('#000000'); return }\n const r = Math.round(best.r / best.count), g = Math.round(best.g / best.count), b = Math.round(best.b / best.count)\n resolve(`#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}`)\n } catch { resolve('#000000') }\n }\n img.onerror = () => resolve('#000000')\n img.src = imageUrl\n })\n}\n","/**\n * Unified Date Utilities for OpenMSP Platform\n * Provides consistent date formatting across all components\n */\n\n/**\n * Relative display label for a ticket timestamp (ISO string).\n * < 1 min → \"Just now\" | 1-59 min → \"X min ago\" | 1-23 h → \"X hour(s) ago\" | 24+ h → \"MM/DD/YYYY\"\n */\nexport function formatTicketRelativeTime(iso: string): string {\n const now = new Date()\n const date = new Date(iso)\n if (Number.isNaN(date.getTime())) return ''\n const diffMs = now.getTime() - date.getTime()\n const diffMin = Math.floor(diffMs / 60_000)\n if (diffMin < 1) return 'Just now'\n if (diffMin < 60) return `${diffMin} min ago`\n const diffHours = Math.floor(diffMin / 60)\n if (diffHours < 24) return diffHours === 1 ? '1 hour ago' : `${diffHours} hours ago`\n const mm = String(date.getMonth() + 1).padStart(2, '0')\n const dd = String(date.getDate()).padStart(2, '0')\n const yyyy = date.getFullYear()\n return `${mm}/${dd}/${yyyy}`\n}\n\n/**\n * Full tooltip timestamp in \"MM/DD/YYYY, H:MM AM/PM\" format.\n */\nexport function formatTicketFullTimestamp(iso: string): string {\n const date = new Date(iso)\n if (Number.isNaN(date.getTime())) return ''\n const mm = String(date.getMonth() + 1).padStart(2, '0')\n const dd = String(date.getDate()).padStart(2, '0')\n const yyyy = date.getFullYear()\n let hours = date.getHours()\n const minutes = String(date.getMinutes()).padStart(2, '0')\n const ampm = hours >= 12 ? 'PM' : 'AM'\n hours = hours % 12 || 12\n return `${mm}/${dd}/${yyyy}, ${hours}:${minutes} ${ampm}`\n}\n\n/**\n * Format relative time from ISO timestamp\n * This is the single source of truth for all relative time formatting\n * \n * @param timestamp - ISO timestamp string or Date object\n * @returns Formatted relative time string\n * \n * @example\n * formatRelativeTime('2024-01-01T12:00:00Z') // '2 hours ago'\n * formatRelativeTime(new Date()) // 'Just now'\n */\nexport function formatRelativeTime(timestamp: string | Date): string {\n const now = new Date();\n const targetTime = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;\n \n // Validate the date\n if (isNaN(targetTime.getTime())) {\n console.warn('⚠️ Invalid timestamp in formatRelativeTime:', timestamp);\n return 'Unknown time';\n }\n \n const diffInMs = now.getTime() - targetTime.getTime();\n const diffInMinutes = Math.floor(diffInMs / (1000 * 60));\n \n // Handle future dates (should not happen but graceful fallback)\n if (diffInMinutes < 0) {\n return 'Just now';\n }\n \n // Less than 1 minute\n if (diffInMinutes < 1) return 'Just now';\n \n // 1-59 minutes\n if (diffInMinutes < 60) return `${diffInMinutes}m ago`;\n \n // 1-23 hours \n const diffInHours = Math.floor(diffInMinutes / 60);\n if (diffInHours < 24) return `${diffInHours}h ago`;\n \n // 1-6 days\n const diffInDays = Math.floor(diffInHours / 24);\n if (diffInDays < 7) return `${diffInDays}d ago`;\n \n // 1-4 weeks\n if (diffInDays < 30) {\n const weeks = Math.floor(diffInDays / 7);\n return `${weeks}w ago`;\n }\n \n // Older than 30 days - show formatted date\n return targetTime.toLocaleDateString('en-US', { \n month: 'short', \n day: 'numeric',\n year: targetTime.getFullYear() !== now.getFullYear() ? 'numeric' : undefined\n });\n}\n\n/**\n * Format absolute date for display\n * \n * @param timestamp - ISO timestamp string or Date object\n * @param options - Intl.DateTimeFormatOptions for customization\n * @returns Formatted date string\n */\nexport function formatAbsoluteDate(\n timestamp: string | Date,\n options: Intl.DateTimeFormatOptions = {}\n): string {\n const targetTime = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;\n \n if (isNaN(targetTime.getTime())) {\n console.warn('⚠️ Invalid timestamp in formatAbsoluteDate:', timestamp);\n return 'Invalid date';\n }\n \n const defaultOptions: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n ...options\n };\n \n return targetTime.toLocaleDateString('en-US', defaultOptions);\n}\n\n/**\n * Format timestamp with time included\n * \n * @param timestamp - ISO timestamp string or Date object\n * @param options - Intl.DateTimeFormatOptions for customization\n * @returns Formatted datetime string\n */\nexport function formatDateTime(\n timestamp: string | Date,\n options: Intl.DateTimeFormatOptions = {}\n): string {\n const targetTime = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;\n \n if (isNaN(targetTime.getTime())) {\n console.warn('⚠️ Invalid timestamp in formatDateTime:', timestamp);\n return 'Invalid date';\n }\n \n const defaultOptions: Intl.DateTimeFormatOptions = {\n year: 'numeric',\n month: 'short',\n day: 'numeric',\n hour: 'numeric',\n minute: '2-digit',\n ...options\n };\n \n return targetTime.toLocaleDateString('en-US', defaultOptions);\n}\n\n/**\n * Get time difference in human readable format (detailed)\n * \n * @param timestamp - ISO timestamp string or Date object\n * @returns Detailed time difference string\n */\nexport function getDetailedTimeDifference(timestamp: string | Date): string {\n const now = new Date();\n const targetTime = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;\n \n if (isNaN(targetTime.getTime())) {\n return 'Invalid date';\n }\n \n const diffInMs = now.getTime() - targetTime.getTime();\n const diffInSeconds = Math.floor(diffInMs / 1000);\n const diffInMinutes = Math.floor(diffInSeconds / 60);\n const diffInHours = Math.floor(diffInMinutes / 60);\n const diffInDays = Math.floor(diffInHours / 24);\n \n if (diffInSeconds < 60) return `${diffInSeconds} seconds ago`;\n if (diffInMinutes < 60) return `${diffInMinutes} minutes ago`;\n if (diffInHours < 24) return `${diffInHours} hours ago`;\n if (diffInDays < 30) return `${diffInDays} days ago`;\n \n const diffInMonths = Math.floor(diffInDays / 30);\n if (diffInMonths < 12) return `${diffInMonths} months ago`;\n \n const diffInYears = Math.floor(diffInDays / 365);\n return `${diffInYears} years ago`;\n}\n\n/**\n * Check if a timestamp is today\n */\nexport function isToday(timestamp: string | Date): boolean {\n const today = new Date();\n const targetTime = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;\n \n return today.toDateString() === targetTime.toDateString();\n}\n\n/**\n * Check if a timestamp is within the last N minutes\n */\nexport function isWithinMinutes(timestamp: string | Date, minutes: number): boolean {\n const now = new Date();\n const targetTime = typeof timestamp === 'string' ? new Date(timestamp) : timestamp;\n const diffInMs = now.getTime() - targetTime.getTime();\n const diffInMinutes = diffInMs / (1000 * 60);\n \n return diffInMinutes <= minutes && diffInMinutes >= 0;\n}\n\n/**\n * Create a UTC timestamp string for database insertion\n */\nexport function createUTCTimestamp(): string {\n return new Date().toISOString();\n} ","'use client'\n\n/**\n * Shared decoder for the `decision_resolved` leading frame the\n * `confirm-tool` route emits as the first chunk of its SSE response.\n *\n * Wire format (matches `use-embedded-chat.ts:270-394`):\n * - Frames are JSON objects separated by `\\0` (NUL).\n * - `\\x1E` (record separator) marks the transition from leading\n * metadata frames into the text body (chat path).\n * - For the tickets-UI path, the request body carries `messages: []`\n * which makes phase-2 (text body) structurally unreachable —\n * `confirm-tool/route.ts:569` skips the `runDocsChat` call. So the\n * stream contains exactly ONE frame (`decision_resolved`) followed\n * by EOF.\n *\n * This helper drains the stream defensively (reads all remaining bytes\n * after the leading frame so the server-side `composed.start()` doesn't\n * hang on a half-closed connection) and returns the first decoded\n * `decision_resolved` frame. Throws on:\n * - Empty body\n * - First frame is not `decision_resolved`\n * - Malformed JSON before the first `\\0`\n *\n * Migration note: `use-embedded-chat.ts` still inlines the full decoder\n * including phase-2 text + trailing usage parsing. A future refactor\n * could move the leading-frame loop here and have the chat shell\n * compose with that — out of scope for this PR.\n */\n\nexport interface DecisionResolvedFrame {\n kind: 'decision_resolved'\n ok: boolean\n action: 'approved' | 'rejected'\n toolName?: string\n willAutoContinue?: boolean\n proposalId?: string\n /** Approve-path result envelope. `mirror_synced=false` means the\n * HubSpot REST call succeeded but the local mirror upsert lagged —\n * callers should optimistic-render and schedule a delayed refetch. */\n result?: {\n ticket_id?: string\n status?: string | null\n mirror_synced?: boolean\n } | null\n card?: {\n type?: string\n marker?: string\n ref?: unknown\n } | null\n receiptText?: string\n}\n\n/**\n * Read the leading `decision_resolved` frame from a confirm-tool SSE\n * response. Drains the stream to end-of-file before resolving so the\n * server doesn't sit on a half-closed socket.\n *\n * @throws {Error} when the body is empty, the first frame is not\n * `decision_resolved`, or the leading JSON is malformed.\n */\nexport async function readLeadingDecisionFrame(\n response: Response,\n): Promise<DecisionResolvedFrame> {\n const reader = response.body?.getReader()\n if (!reader) throw new Error('readLeadingDecisionFrame: response has no body')\n\n const decoder = new TextDecoder()\n let buffer = ''\n let frame: DecisionResolvedFrame | null = null\n\n try {\n while (true) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n\n // Try to extract the leading frame ASAP — we only need the first\n // chunk that contains a `\\0`. Everything after is either phase-2\n // text (unexpected for tickets-UI but tolerated) or end-of-stream.\n if (frame === null) {\n const nullIdx = buffer.indexOf('\\0')\n const recIdx = buffer.indexOf('\\x1E')\n // If `\\x1E` (text-body marker) shows up before `\\0`, the server\n // emitted phase-2 without a leading frame — protocol violation.\n if (recIdx !== -1 && (nullIdx === -1 || recIdx < nullIdx)) {\n throw new Error(\n 'readLeadingDecisionFrame: text-body sentinel arrived before leading frame',\n )\n }\n if (nullIdx !== -1) {\n const raw = buffer.slice(0, nullIdx)\n let parsed: unknown\n try {\n parsed = JSON.parse(raw)\n } catch (err) {\n throw new Error(\n `readLeadingDecisionFrame: leading JSON parse failed: ${(err as Error).message}`,\n )\n }\n const obj = parsed as Record<string, unknown>\n if (obj?.kind !== 'decision_resolved') {\n throw new Error(\n `readLeadingDecisionFrame: expected decision_resolved, got kind=${String(obj?.kind)}`,\n )\n }\n frame = normalizeDecisionFrame(obj)\n // Continue draining so the server-side stream closes cleanly.\n buffer = buffer.slice(nullIdx + 1)\n }\n }\n // Past the leading frame: just consume + discard the remainder.\n // Don't allocate a parser for phase-2 text; the tickets-UI path\n // is supposed to be a single frame.\n }\n } finally {\n // Release the lock even if we threw mid-loop.\n try {\n reader.releaseLock()\n } catch {\n // No-op: reader may already be released if the stream ended cleanly.\n }\n }\n\n if (frame === null) {\n throw new Error('readLeadingDecisionFrame: stream closed before leading frame arrived')\n }\n return frame\n}\n\nfunction normalizeDecisionFrame(obj: Record<string, unknown>): DecisionResolvedFrame {\n const action = obj.action === 'rejected' ? 'rejected' : 'approved'\n const result = (obj.result ?? null) as DecisionResolvedFrame['result']\n const card = (obj.card ?? null) as DecisionResolvedFrame['card']\n return {\n kind: 'decision_resolved',\n ok: obj.ok === true,\n action,\n ...(typeof obj.toolName === 'string' ? { toolName: obj.toolName } : {}),\n ...(obj.willAutoContinue === true ? { willAutoContinue: true } : {}),\n ...(typeof obj.proposalId === 'string' ? { proposalId: obj.proposalId } : {}),\n ...(result ? { result } : {}),\n ...(card ? { card } : {}),\n ...(typeof obj.receiptText === 'string' ? { receiptText: obj.receiptText } : {}),\n }\n}\n","// Announcement system TypeScript types\nimport type { LegacyPlatform, PlatformFilter, PlatformRecord } from './platform';\n\n// For backward compatibility, maintain the old Platform type temporarily\nexport type IconType = 'svg' | 'png';\n\n// Database Models\nexport interface Announcement {\n id: string;\n title: string;\n description: string;\n background_color: string;\n icon_type: IconType;\n icon_svg_name?: string;\n icon_png_url?: string;\n /** Extra props for the main bar/icon */\n icon_svg_props?: Record<string, any>;\n platform_id: string; // Foreign key to platforms table\n platform?: PlatformRecord; // Joined platform data\n is_active: boolean;\n // CTA (Call-To-Action) fields\n cta_enabled?: boolean;\n cta_text?: string;\n cta_icon?: string;\n cta_show_icon?: boolean;\n cta_url?: string;\n cta_target?: '_self' | '_blank';\n // Custom CTA button colors (hex codes like #FFFFFF). Optional.\n cta_button_background_color?: string | null;\n cta_button_text_color?: string | null;\n /** Additional props to spread onto the CTA icon component */\n cta_icon_props?: Record<string, any>;\n created_at: string;\n updated_at: string;\n created_by?: string;\n created_by_user?: {\n id: string;\n email: string;\n full_name?: string;\n };\n announcement_assets?: AnnouncementAsset[];\n}\n\nexport interface AnnouncementAsset {\n id: string;\n announcement_id: string;\n file_name: string;\n file_url: string;\n file_size: number;\n mime_type: string;\n upload_path: string;\n created_at: string;\n}\n\n// API Request/Response Types\nexport interface CreateAnnouncementData {\n title: string;\n description: string;\n background_color: string;\n icon_type: IconType;\n icon_svg_name?: string;\n icon_png_url?: string;\n /** Extra props for the main bar/icon */\n icon_svg_props?: Record<string, any>;\n platforms?: string[]; // entity_platforms set (multi-select)\n is_active?: boolean;\n // CTA (Call-To-Action) fields\n cta_enabled?: boolean;\n cta_text?: string;\n cta_icon?: string;\n cta_show_icon?: boolean;\n cta_url?: string;\n cta_target?: '_self' | '_blank';\n cta_button_background_color?: string;\n cta_button_text_color?: string;\n cta_icon_props?: Record<string, any>;\n created_by: string;\n}\n\nexport interface UpdateAnnouncementData {\n title?: string;\n description?: string;\n background_color?: string;\n icon_type?: IconType;\n icon_svg_name?: string;\n icon_png_url?: string;\n /** Extra props for the main bar/icon */\n icon_svg_props?: Record<string, any>;\n platforms?: string[]; // entity_platforms set (multi-select)\n is_active?: boolean;\n // CTA (Call-To-Action) fields\n cta_enabled?: boolean;\n cta_text?: string;\n cta_icon?: string;\n cta_show_icon?: boolean;\n cta_url?: string;\n cta_target?: '_self' | '_blank';\n cta_button_background_color?: string;\n cta_button_text_color?: string;\n cta_icon_props?: Record<string, any>;\n}\n\nexport interface AnnouncementListResponse {\n announcements: Announcement[];\n pagination: {\n page: number;\n page_size: number;\n total_count: number;\n total_pages: number;\n };\n filters?: {\n platform?: LegacyPlatform;\n is_active?: boolean;\n };\n}\n\nexport interface AnnouncementResponse {\n announcement: Announcement;\n}\n\n// Form Data Types\nexport interface AnnouncementFormData {\n title: string;\n description: string;\n background_color: string;\n icon_type: IconType;\n icon_svg_name: string;\n icon_png_file?: File;\n platforms: string[]; // entity_platforms set (multi-select; no single platform_id)\n is_active: boolean;\n // CTA (Call-To-Action) fields\n cta_enabled: boolean;\n cta_text: string;\n cta_icon: string;\n cta_show_icon: boolean;\n cta_url: string;\n cta_target: '_self' | '_blank';\n // New CTA button custom colors\n cta_button_background_color?: string;\n cta_button_text_color?: string;\n cta_icon_props?: string; // JSON string in form\n /** Extra props for the main bar/icon */\n icon_svg_props?: string;\n}\n\n// API Query Parameters\nexport interface GetAnnouncementsOptions {\n platform?: PlatformFilter; // Changed from Platform to PlatformFilter to include 'all'\n is_active?: boolean;\n page?: number;\n pageSize?: number;\n sortBy?: 'created_at' | 'updated_at' | 'title';\n sortOrder?: 'asc' | 'desc';\n}\n\n// File Upload Types\nexport interface IconUploadResponse {\n success: boolean;\n file_url?: string;\n file_name?: string;\n file_size?: number;\n error?: string;\n}\n\nexport interface IconUploadData {\n file: File;\n announcement_id?: string;\n}\n\n// Available SVG Icons (extend as needed)\nexport interface AvailableSvgIcon {\n name: string;\n display_name: string;\n component_name: string;\n}\n\n// Error Types\nexport interface AnnouncementError {\n code: string;\n message: string;\n details?: string;\n}\n\n// Validation Types\nexport interface AnnouncementValidation {\n title: {\n required: boolean;\n minLength: number;\n maxLength: number;\n };\n description: {\n required: boolean;\n minLength: number;\n maxLength: number;\n };\n background_color: {\n required: boolean;\n pattern: RegExp;\n };\n icon_svg_name: {\n required: boolean;\n validOptions: string[];\n };\n platform: {\n required: boolean;\n validOptions: LegacyPlatform[];\n };\n}\n\n// Import unified platform configuration (removed duplicate definition)\nexport type { PlatformConfig } from './platform';\n\n// Admin Dashboard Types\nexport interface AnnouncementStats {\n total_announcements: number;\n active_announcements: number;\n announcements_by_platform: Record<LegacyPlatform, number>;\n recent_announcements: Announcement[];\n}\n\n// Component Props Types\nexport interface AnnouncementBarProps {\n announcement?: Announcement;\n onDismiss?: (announcementId: string) => void;\n className?: string;\n}\n\nexport interface AnnouncementFormProps {\n announcement?: Announcement;\n onSubmit: (data: AnnouncementFormData) => Promise<void>;\n onCancel: () => void;\n isLoading?: boolean;\n availableIcons: AvailableSvgIcon[];\n}\n\nexport interface AnnouncementListProps {\n announcements: Announcement[];\n onEdit: (announcement: Announcement) => void;\n onDelete: (announcementId: string) => void;\n onToggleActive: (announcementId: string, isActive: boolean) => void;\n isLoading?: boolean;\n}\n\n// Database Utility Types\nexport interface AnnouncementFilters {\n platform?: LegacyPlatform;\n is_active?: boolean;\n created_by?: string;\n search?: string;\n}\n\nexport interface AnnouncementSortOptions {\n field: 'created_at' | 'updated_at' | 'title' | 'platform';\n direction: 'asc' | 'desc';\n}\n\n// SVG Icon Type\nexport interface SvgIcon {\n name: string;\n label: string;\n component: any;\n}\n\n// Platform display configuration\n// PLATFORM_CONFIGS removed - all platform data now comes from database via getPlatformsConfig()\n\n// Available SVG icons configuration\nexport const AVAILABLE_SVG_ICONS: SvgIcon[] = [\n // OpenFrame Logo Options\n { name: 'openframe-logo', label: 'OpenFrame Logo', component: null as any },\n \n // Platform Logos\n { name: 'openmsp-logo', label: 'OpenMSP Logo', component: null as any },\n { name: 'flamingo-logo', label: 'Flamingo Logo', component: null as any },\n \n // Lucide Icons\n { name: 'megaphone', label: 'Megaphone', component: null as any },\n { name: 'bell', label: 'Bell', component: null as any },\n { name: 'info', label: 'Information', component: null as any },\n { name: 'star', label: 'Star', component: null as any },\n { name: 'rocket', label: 'Rocket', component: null as any },\n { name: 'package', label: 'Package', component: null as any }\n]; ","// Product Release Types\n// Following the blog.ts pattern for consistency\n\nimport type { PlatformRecord } from './platform'\nimport type { EntityAuthor } from './entity-author'\nimport type { BlogTag, TagAssoc } from './blog'\nimport type { VideoTeaser, TranscriptWord, SpeakerMapping } from './video-processing'\n\nexport interface ChangelogEntry {\n title: string\n description?: string\n /**\n * Per-entry visibility flag. Optional so product releases (which always render\n * publicly) remain backwards compatible. Investor updates require this field\n * because each entry can be marked internal — see investor-update-utils.ts.\n */\n visibility?: 'public' | 'internal'\n}\n\nexport interface ReleaseMedia {\n id: number\n release_id: number\n media_type: 'image' | 'video' | 'screenshot' | 'demo'\n media_url: string\n display_order: number | null\n title: string | null\n description: string | null\n created_at: string\n updated_at: string\n created_by: string\n}\n\nexport interface ProductReleaseGitHubLink {\n id: number\n release_id: number\n github_release_url: string // Full URL (e.g., https://github.com/org/repo/releases/tag/v2.0.0)\n display_order: number | null\n created_at: string\n}\n\nexport interface KnowledgeBaseLink {\n id: number\n release_id: number\n kb_article_path: string // Relative path (e.g., /api/authentication/api-keys.md)\n display_order: number | null\n created_at: string\n}\n\nexport interface ClickUpRoadmapTask {\n id: number\n release_id: number\n clickup_task_id: string // Just the task ID - details from roadmap folder\n display_order: number | null\n created_at: string\n}\n\nexport interface ClickUpDeliveryTask {\n id: number\n release_id: number\n clickup_task_id: string // Just the task ID - details from delivery list\n display_order: number | null\n created_at: string\n}\n\nexport interface ProductRelease {\n id: number\n title: string\n slug: string\n version: string\n summary: string | null\n content: string | null\n\n // Release metadata\n release_type: 'major' | 'minor' | 'patch' | 'beta' | 'alpha'\n release_status: 'alpha' | 'beta' | 'stable' | 'deprecated'\n release_date: string\n\n // Changelog (JSONB arrays)\n features_added: ChangelogEntry[]\n bugs_fixed: ChangelogEntry[]\n improvements: ChangelogEntry[]\n breaking_changes: ChangelogEntry[]\n\n // External relationships (one-to-many)\n github_releases?: ProductReleaseGitHubLink[]\n knowledge_base_links?: KnowledgeBaseLink[]\n clickup_roadmap_tasks?: ClickUpRoadmapTask[]\n clickup_delivery_tasks?: ClickUpDeliveryTask[]\n release_media?: ReleaseMedia[]\n\n // Media (keeping featured_image for hero)\n featured_image: string | null\n\n // Documentation\n migration_guide_url: string | null\n documentation_url: string | null\n\n // Video (youtube_url for embeds, main_video_url for AI processing)\n youtube_url: string | null\n main_video_url: string | null\n transcript: string | null\n transcript_words_data?: TranscriptWord[]\n srt_content?: string | null\n video_summary: string | null // AI-generated summary from video transcription\n video_bites: VideoTeaser[] // JSONB array of extracted video clips\n highlight_video_url: string | null\n highlight_video_thumbnail: string | null\n highlight_video_duration_ms: number | null\n highlight_video_source: 'manual' | 'ai_generated' | null\n main_video_thumbnail: string | null\n ai_transcript_formatted?: string\n speaker_mapping?: SpeakerMapping\n ai_confidence_transcript: number | null\n ai_confidence_video_summary: number | null // Confidence for video-generated summary\n ai_confidence_summary: number | null // Confidence for entity summary (Release Content Generation)\n\n // SEO\n seo_title: string | null\n seo_description: string | null\n seo_keywords: string | null\n og_image_url: string | null\n\n // Publishing\n status: 'draft' | 'published' | 'archived'\n published_at: string | null\n author_id: string | null\n /**\n * Hydrated by `getRelease` (detail) and `getReleases` (list) via\n * `hydrateAuthor` in the hub DAL. Optional so admin form + every existing\n * consumer that doesn't read author stay backward-compatible.\n */\n author?: EntityAuthor\n\n // Timestamps\n created_at: string\n updated_at: string\n\n // Editor-provided AI generation instructions (steers Generate Content prompt)\n custom_instructions: string | null\n\n // Analytics\n view_count: number\n download_count: number\n\n // Relations (populated by joins)\n platforms?: PlatformRecord[]\n tags?: BlogTag[]\n product_release_platforms?: Array<{\n platform_id: string\n is_featured: boolean\n featured_order: number | null\n // The release DAL flattens the joined `platforms` row onto each junction\n // entry (`...platform.platforms`), so `name`/`display_name` are present at\n // runtime — `composeContentUrlFromPlatforms` reads `name` to resolve the\n // target platform. Declared here so callers can pass this array to the\n // `composeContentUrl` seam without a cast.\n name?: string\n display_name?: string\n }>\n // Flat unified tag-association shape (hydrated by entity-tag-utils).\n product_release_tags?: TagAssoc[]\n}\n\nexport interface CreateProductReleaseData {\n title: string\n slug: string\n version: string\n summary?: string\n content?: string\n release_type: 'major' | 'minor' | 'patch' | 'beta' | 'alpha'\n release_status: 'alpha' | 'beta' | 'stable' | 'deprecated'\n release_date: string\n features_added?: ChangelogEntry[]\n bugs_fixed?: ChangelogEntry[]\n improvements?: ChangelogEntry[]\n breaking_changes?: ChangelogEntry[]\n github_releases?: Array<{ github_release_url: string; display_order?: number }>\n knowledge_base_links?: Array<{ kb_article_path: string; display_order?: number }>\n clickup_roadmap_tasks?: Array<{ clickup_task_id: string; display_order?: number }>\n clickup_delivery_tasks?: Array<{ clickup_task_id: string; display_order?: number }>\n release_media?: Array<{ media_type: 'image' | 'video' | 'screenshot' | 'demo'; media_url: string; title?: string; description?: string; display_order?: number }>\n featured_image?: string\n migration_guide_url?: string\n documentation_url?: string\n youtube_url?: string\n main_video_url?: string\n transcript?: string\n srt_content?: string | null\n video_summary?: string // AI-generated summary from video transcription\n video_bites?: VideoTeaser[]\n highlight_video_url?: string | null\n highlight_video_thumbnail?: string | null\n highlight_video_duration_ms?: number | null\n highlight_video_source?: 'manual' | 'ai_generated' | null\n main_video_thumbnail?: string | null\n seo_title?: string\n seo_description?: string\n seo_keywords?: string\n og_image_url?: string\n status: 'draft' | 'published' | 'archived'\n published_at?: string | null\n author_id: string\n platforms: string[] // Array of platform IDs (UUIDs)\n featured_platform?: string // Platform ID for featured\n tags: number[] // Array of tag IDs\n custom_instructions?: string | null\n}\n\nexport type UpdateProductReleaseData = Partial<CreateProductReleaseData>\n\nexport interface ProductReleaseFilters {\n platform?: string | 'all'\n tags?: string[]\n version?: string\n release_type?: ('major' | 'minor' | 'patch' | 'beta' | 'alpha')[]\n release_status?: ('alpha' | 'beta' | 'stable' | 'deprecated')[]\n has_breaking_changes?: boolean\n search?: string\n featured?: boolean\n status?: string\n limit?: number\n offset?: number\n ids?: (number | string)[]\n}\n\nexport interface ProductReleaseListResponse {\n data: ProductRelease[]\n count: number\n}\n\n// GitHub Release interface (for wizard)\nexport interface GitHubRelease {\n id: string\n tag_name: string\n name: string\n body: string\n published_at: string\n html_url: string\n prerelease: boolean\n draft: boolean\n}\n\n// Release type options for dropdowns\nexport const releaseTypeOptions = [\n { value: 'major', label: 'Major Release', description: 'Breaking changes, new architecture', color: 'red' },\n { value: 'minor', label: 'Minor Release', description: 'New features, backward compatible', color: 'blue' },\n { value: 'patch', label: 'Patch Release', description: 'Bug fixes only', color: 'green' },\n { value: 'beta', label: 'Beta Release', description: 'Pre-release testing version', color: 'yellow' },\n { value: 'alpha', label: 'Alpha Release', description: 'Early testing version', color: 'orange' }\n] as const\n\n// Release status options\nexport const releaseStatusOptions = [\n { value: 'alpha', label: 'Alpha', description: 'Early development, unstable', color: 'orange' },\n { value: 'beta', label: 'Beta', description: 'Feature complete, testing', color: 'yellow' },\n { value: 'stable', label: 'Stable', description: 'Production ready', color: 'green' },\n { value: 'deprecated', label: 'Deprecated', description: 'No longer supported', color: 'gray' }\n] as const\n\n// Changelog category labels\nexport const changelogLabels = {\n features_added: 'Features Added',\n bugs_fixed: 'Bugs Fixed',\n improvements: 'Improvements',\n breaking_changes: 'Breaking Changes'\n} as const\n\n// Semantic versioning validation regex\nexport const SEMVER_REGEX = /^\\d+\\.\\d+\\.\\d+(-[a-zA-Z0-9.-]+)?(\\+[a-zA-Z0-9.-]+)?$/\n","/**\n * Delivery (ClickUp Bug-fixes & Enhancements) wire types — shared between the\n * hub's `/api/delivery*` route shapes and the lib's `DeliveryTable`/`DeliveryLists`/\n * `DeliverySection` components.\n *\n * Lifted from hub `types/delivery.ts` so embedders consuming the lib's\n * delivery surfaces and the lib's own `ReleaseDetailPage` (which renders\n * related delivery items) share one canonical shape.\n */\n\nexport interface DeliveryItem {\n id: string;\n title: string;\n description: string;\n status: string;\n statusColor: string; // ClickUp status color\n taskType: 'Request' | 'Bug' | string; // ClickUp task type\n /**\n * Canonical ClickUp custom_item_id (1008 = Bug, 1009 = Request, …).\n * Surfaced here so the chat's compact card can render a type-specific\n * lucide icon via `TaskTypeIcon` instead of the two-letter initials\n * fallback. Single source of truth lives in\n * `lib/utils/clickup-task-type-utils.ts` (hub-side).\n */\n customItemId: number | null;\n /**\n * Every ClickUp list the task is associated with (home list + ClickUp's\n * \"Tasks in Multiple Lists\" locations). UI joins these for display.\n * Falls back to a single-element array containing the home list when\n * there are no additional locations.\n */\n listNames: string[];\n dateOpened: number; // Unix timestamp\n dateUpdated: number; // Unix timestamp\n dateClosed: number | null; // Unix timestamp or null if not closed\n clickupUrl: string;\n}\n\nexport interface DeliveryResponse {\n completed: DeliveryItem[];\n inProgress: DeliveryItem[];\n}\n\n// Task type to badge label mapping\nexport const TASK_TYPE_LABELS = {\n Request: 'ENHANCEMENT',\n Bug: 'BUG-FIX',\n} as const;\n\n// Task type to badge text-color mapping (ODS attention-red for Bug; default for others)\nexport const TASK_TYPE_TEXT_COLORS = {\n Request: '', // Default white/grey\n Bug: 'text-[var(--ods-attention-red-error)]',\n} as const;\n","/**\n * TMCG (The Miami Cyber Gang) related types and interfaces\n */\n\nexport interface TMCGMemberSocialLink {\n platform: string\n url: string\n username?: string\n}\n\nexport interface TMCGMember {\n id: string\n full_name: string\n avatar_url?: string | null\n company?: string | null\n about?: string | null\n job_title?: string | null\n tmcg_roles: string[]\n social_links: TMCGMemberSocialLink[]\n created_at: string\n updated_at: string\n}\n\nexport interface TMCGMembersResponse {\n success: boolean\n data: {\n members: TMCGMember[]\n count: number\n }\n error?: string\n}\n\nexport interface TMCGMemberResponse {\n success: boolean\n data: TMCGMember\n error?: string\n}\n\n/**\n * TMCG Member Card Props\n */\nexport interface TMCGMemberCardProps {\n member: TMCGMember\n className?: string\n compact?: boolean\n}\n\n/**\n * TMCG Members Grid Props\n */\nexport interface TMCGMembersGridProps {\n members: TMCGMember[]\n isLoading?: boolean\n error?: string | null\n className?: string\n}\n\n/**\n * TMCG Social Links Props\n */\nexport interface TMCGSocialLinksProps {\n socialLinks: TMCGMemberSocialLink[]\n className?: string\n size?: 'sm' | 'md' | 'lg'\n}\n\n/**\n * Hook return types\n */\nexport interface UseTMCGMembersResult {\n members: TMCGMember[]\n isLoading: boolean\n error: string | null\n refetch: () => void\n}\n\nexport interface UseTMCGMemberResult {\n member: TMCGMember | null\n isLoading: boolean\n error: string | null\n refetch: () => void\n}\n\n/**\n * TMCG specific constants\n */\nexport const TMCG_ROLES = {\n FOUNDER: 'founder',\n ORGANIZER: 'organizer',\n MEMBER: 'member',\n CONTRIBUTOR: 'contributor',\n SPEAKER: 'speaker',\n MENTOR: 'mentor'\n} as const\n\nexport type TMCGRole = typeof TMCG_ROLES[keyof typeof TMCG_ROLES]\n\n/**\n * TMCG role display names\n */\nexport const TMCG_ROLE_DISPLAY_NAMES: Record<string, string> = {\n [TMCG_ROLES.FOUNDER]: 'Founder',\n [TMCG_ROLES.ORGANIZER]: 'Organizer',\n [TMCG_ROLES.MEMBER]: 'Member',\n [TMCG_ROLES.CONTRIBUTOR]: 'Contributor',\n [TMCG_ROLES.SPEAKER]: 'Speaker',\n [TMCG_ROLES.MENTOR]: 'Mentor'\n}\n\n/**\n * Social platform icon mapping for TMCG context\n */\nexport const TMCG_SOCIAL_PLATFORMS = {\n linkedin: 'LinkedIn',\n twitter: 'Twitter',\n github: 'GitHub',\n website: 'Website',\n youtube: 'YouTube',\n instagram: 'Instagram',\n facebook: 'Facebook',\n discord: 'Discord',\n telegram: 'Telegram',\n slack: 'Slack'\n} as const\n\nexport type TMCGSocialPlatform = keyof typeof TMCG_SOCIAL_PLATFORMS","/**\n * Canonical per-section metadata for the OpenFrame dev-center surfaces\n * (Roadmap / Bug-fixes & Enhancements / Releases / Onboarding Guides).\n *\n * One file co-locates every per-section value the openframe homepage\n * navigator card AND the destination full-page hero both consume —\n * icon, brief navigator description, longer hero paragraph, search\n * config (placeholder + paramKey), filter config (paramKey + options).\n *\n * Adding a 5th section is one edit here and zero edits across the\n * page files / navigator grid.\n *\n * EMBEDDER NOTE — `onboarding`:\n * The `onboarding` entry is INERT on `hero`/`search`/`filter` because\n * `/onboarding-guides` is owned by `OnboardingGuidesCatalogView` (a\n * hub-side full-page view), not the shared `DevSectionView` chrome.\n * This entry exists ONLY to drive the homepage navigator card. Any\n * embedder iterating the registry must SKIP `onboarding` if they\n * don't host the `/onboarding-guides` route — e.g.\n * `Object.values(OPENFRAME_DEV_SECTIONS).filter(s => s.href !== '/onboarding-guides')`.\n *\n * SERVER-BUNDLE SAFETY:\n * This module imports lucide-react icon components as JSX values but\n * never RENDERS them at module load. Lib's tsup config places `utils/`\n * in the server bundle (no `\"use client\"` banner), which is safe\n * because component-function references don't execute on import.\n * Precedent: `src/utils/platform-config.tsx` (also lucide imports,\n * also server-bundle, also imported by route-page `metadata` exports).\n */\n\nimport { Map as MapIcon, Wrench, Rocket, GraduationCap, LifeBuoy, type LucideIcon } from 'lucide-react'\nimport { releaseStatusOptions } from '../../types'\nimport { DEV_SECTION_PARAM_KEYS } from './dev-section-param-keys'\n\n// Roadmap status options — `as const` preserves readonly tuple typing\n// across the registry boundary.\nexport const ROADMAP_STATUS_OPTIONS = [\n { value: 'all', label: 'All' },\n { value: 'completed', label: 'Completed' },\n { value: 'in_progress', label: 'In Progress' },\n] as const\n\n// Delivery (ClickUp custom item type) filter options. `Bug` and `Request`\n// are the ClickUp `custom_item_id` labels — 1008 / 1009.\nexport const DELIVERY_TASK_TYPE_OPTIONS = [\n { value: 'all', label: 'All' },\n { value: 'Bug', label: 'Bug-fix' },\n { value: 'Request', label: 'Enhancement' },\n] as const\n\n// Ticket status filter for the Help Center. Lowercase wire values\n// match what `/api/chat/agent/find-ticket` accepts in `body.status`\n// (the route normalizes to lowercase + allowlists 'open' | 'closed'\n// before threading into `findTicketExecutor`'s `selfStatus` scope).\nexport const TICKET_STATUS_OPTIONS = [\n { value: 'all', label: 'All' },\n { value: 'open', label: 'Open' },\n { value: 'closed', label: 'Closed' },\n] as const\n\nexport interface OpenframeDevSection {\n /** Route href the navigator card and any internal cross-link composes. */\n href: string\n icon: LucideIcon\n /** Brief copy for the homepage navigator card (≈50 char teaser). */\n navigator: {\n title: string\n description: string\n }\n /** Longer copy for the destination page hero (≈120-150 char marketing paragraph). */\n hero: {\n title: string\n description: string\n }\n /** Inline search bar configuration. */\n search: {\n /** Placeholder text shown in the search input. */\n placeholder: string\n /** URL search param the input writes on submit and the list reads on fetch. */\n paramKey: string\n } | null\n /** Filter pill row configuration. `null` when the section has no filter (e.g. onboarding-guides). */\n filter: {\n label: string\n /** URL search param the pills write and the list reads. */\n paramKey: string\n /** The option value that maps to \"no filter applied\". When selected,\n * the URL param is removed instead of being set. Defaults to the\n * first option's value if omitted — explicit field guards against\n * brittleness if `options` is later reordered. */\n defaultValue: string\n options: readonly { value: string; label: string }[]\n } | null\n}\n\nexport const OPENFRAME_DEV_SECTIONS = {\n roadmap: {\n href: '/roadmap',\n icon: MapIcon,\n navigator: {\n title: 'Development Roadmap',\n description: \"What we're building next — vote on upcoming features.\",\n },\n hero: {\n title: 'Development Roadmap',\n description:\n \"See what's in flight, what's planned, and what's up for community vote. The entire OpenFrame roadmap is public.\",\n },\n search: { placeholder: 'Search roadmap items...', paramKey: DEV_SECTION_PARAM_KEYS.search },\n filter: { label: 'Status', paramKey: DEV_SECTION_PARAM_KEYS.status, defaultValue: 'all', options: ROADMAP_STATUS_OPTIONS },\n },\n delivery: {\n href: '/bug-fixes-and-enhancements',\n icon: Wrench,\n navigator: {\n title: 'Bug-fixes & Enhancements',\n description: 'Recently shipped fixes and improvements.',\n },\n hero: {\n title: 'Bug-fixes & Enhancements',\n description:\n 'A running log of fixes and improvements shipping into OpenFrame — recently completed and actively in progress.',\n },\n search: { placeholder: 'Search tasks...', paramKey: DEV_SECTION_PARAM_KEYS.search },\n filter: { label: 'Type', paramKey: DEV_SECTION_PARAM_KEYS.deliveryTaskType, defaultValue: 'all', options: DELIVERY_TASK_TYPE_OPTIONS },\n },\n releases: {\n href: '/releases',\n icon: Rocket,\n navigator: {\n title: 'Product Releases',\n description: 'Version history and release notes.',\n },\n hero: {\n title: 'Product Releases',\n description:\n 'Version notes, change summaries, and stability tier (alpha / beta / stable) for every OpenFrame release.',\n },\n search: { placeholder: 'Search releases...', paramKey: DEV_SECTION_PARAM_KEYS.search },\n filter: { label: 'Status', paramKey: DEV_SECTION_PARAM_KEYS.releaseStatus, defaultValue: 'all', options: releaseStatusOptions },\n },\n onboarding: {\n href: '/onboarding-guides',\n icon: GraduationCap,\n navigator: {\n title: 'Onboarding Guides',\n description: 'Step-by-step product walkthroughs.',\n },\n hero: {\n title: 'Getting Started',\n description:\n 'Step-by-step walkthroughs for getting up and running with OpenFrame — from your first device deployment to advanced integrations.',\n },\n // `search` / `filter` are intentionally null — onboarding-guides\n // uses a custom RAG-backed search dropdown (`useDocSearch`, not the\n // local text filter `DevSectionView` ships) and dynamic section\n // pills (counts vary with content, vs the static status options\n // every other dev-center surface uses). Both controls are injected\n // by the host via `DevSectionPage`'s `preControls` slot — same\n // mechanism tickets uses for its \"Open a new ticket\" form.\n search: null,\n filter: null,\n },\n tickets: {\n href: '/tickets',\n icon: LifeBuoy,\n navigator: {\n title: 'Help Center',\n description: 'Open and manage your support tickets.',\n },\n hero: {\n title: 'Help Center',\n description:\n 'Open new tickets, follow up on existing ones, and track responses from the team — all in one place.',\n },\n search: { placeholder: 'Search your tickets...', paramKey: DEV_SECTION_PARAM_KEYS.search },\n filter: {\n label: 'Status',\n paramKey: DEV_SECTION_PARAM_KEYS.status,\n defaultValue: 'all',\n options: TICKET_STATUS_OPTIONS,\n },\n },\n} as const satisfies Record<string, OpenframeDevSection>\n\nexport type OpenframeDevSectionKey = keyof typeof OPENFRAME_DEV_SECTIONS\n","/**\n * `scrollElementIntoView` — canonical \"scroll an element to the top of the\n * viewport, account for sticky chrome, survive layout shifts\" helper.\n *\n * One shared implementation so every caller (the ticket drawer expand, the\n * hub's `useUnifiedNav` / `use-nav-link` hash scroll, doc-tree, delivery\n * `?focus=`, sticky-section-nav, …) inherits the SAME cancellation-proof\n * motion.\n *\n * WHY A SELF-DRIVEN rAF TWEEN INSTEAD OF `window.scrollTo({behavior:'smooth'})`:\n * the native smooth scroll is CANCELLABLE, and in real pages it gets cancelled\n * constantly:\n *\n * - Browser SCROLL ANCHORING: when content is inserted/removed above or\n * around the target (a collapsible drawer expanding, an async image\n * loading, a list re-rendering) the browser issues a synchronous scrollTop\n * correction to keep the anchored element stable. Per CSSOM-View \"perform a\n * scroll\" step 1 (\"abort any ongoing smooth scroll\"), that correction\n * ABORTS an in-flight native smooth scroll — so it lands as an instant jump.\n * Anchoring is suppressed when the scroll offset is 0, which is exactly why\n * a native smooth scroll appears to work the FIRST time (page at top) and\n * jumps on every repeat (page already scrolled). This was a multi-day\n * \"smooth only works once\" bug on the /tickets drawer.\n * - A second programmatic scroll on the same frame, or a `focus()` without\n * `{preventScroll:true}`, cancels it the same way.\n *\n * A tween that re-asserts the position with INSTANT writes every frame is\n * immune: there is no \"ongoing native smooth scroll\" for anchoring/focus to\n * abort, and any correction that lands between our frames is overwritten on the\n * next frame. We also RECOMPUTE the target each frame, so an element whose\n * final position is still settling (drawer still expanding, images loading)\n * is tracked to its resting place instead of animating to a stale pixel.\n *\n * Honors `prefers-reduced-motion` (jumps instantly) and cancels on genuine user\n * scroll intent (wheel / touch) so we never fight the user.\n *\n * WINDOW *OR* A SCROLLABLE ANCESTOR: the helper is not hard-wired to the window\n * scroller. It walks up from the target to the nearest ancestor that is an\n * actual scroll container (`overflow-y: auto | scroll | overlay` AND\n * `scrollHeight > clientHeight`) and drives THAT element; only when none exists\n * does it fall back to `window`. This is what makes it work inside app shells\n * that put page content in a fixed-height `<main class=\"overflow-y-auto\">`\n * (e.g. OpenFrame's `AppLayout`) where the document/window never scrolls — the\n * old window-only version was a silent no-op there. Note `overflow: clip` /\n * `hidden` are deliberately NOT treated as scroll containers, so a list wrapper\n * that uses `overflow-clip` only to round its corners still bubbles the scroll\n * up to the real container (matches the `<HelpCenterCard>` list intent).\n */\n\nexport interface ScrollElementIntoViewOptions {\n /** Pixels to subtract from the target element's `top` so it lands BELOW\n * sticky chrome. Defaults to 0. Pass `96` for the standard hub header. */\n headerOffset?: number\n /** `'smooth'` (default) runs the self-driven tween; `'instant'` / `'auto'`\n * jump in one synchronous write (deep-link land, programmatic focus moves). */\n behavior?: ScrollBehavior\n /** Optional adjustment applied to the computed pixel target each frame. The\n * callback receives the \"raw\" Y (`element.top + scrollY - headerOffset`) and\n * returns the FINAL target. Use when the caller knows about a layout shift\n * (e.g. a sibling drawer collapsing) the geometry can't yet reflect. */\n adjustTargetY?: (rawTargetY: number) => number\n /** Tween duration in ms (smooth only). Default 320. */\n durationMs?: number\n}\n\n/** Module-level handle to the in-flight tween so a new call (or a user\n * gesture) cancels the previous one — only ever one page-scroll animation at\n * a time. */\nlet activeRaf = 0\nlet teardownActive: (() => void) | null = null\n\nfunction cancelActiveScroll(): void {\n if (activeRaf) {\n cancelAnimationFrame(activeRaf)\n activeRaf = 0\n }\n if (teardownActive) {\n teardownActive()\n teardownActive = null\n }\n}\n\nconst easeOutCubic = (t: number): number => 1 - Math.pow(1 - t, 3)\n\n/** Nearest ancestor that is a *real* scroll container, or `null` when the\n * window/document is the scroller. Only `auto | scroll | overlay` count —\n * `clip` / `hidden` are intentionally excluded (a wrapper using `overflow-clip`\n * purely to round corners must let the scroll bubble to the page). */\nfunction getScrollableAncestor(el: HTMLElement): HTMLElement | null {\n for (let node = el.parentElement; node; node = node.parentElement) {\n const overflowY = getComputedStyle(node).overflowY\n if (\n (overflowY === 'auto' || overflowY === 'scroll' || overflowY === 'overlay') &&\n node.scrollHeight > node.clientHeight\n ) {\n return node\n }\n }\n return null\n}\n\n/**\n * Scroll the page so `target` lands at the top of the viewport (below sticky\n * chrome via `headerOffset`). SSR-safe; `null`/`undefined` target is a no-op so\n * callers can pass refs without defensive branching.\n */\nexport function scrollElementIntoView(\n target: HTMLElement | null | undefined,\n options: ScrollElementIntoViewOptions = {},\n): void {\n if (typeof window === 'undefined' || !target) return\n const { headerOffset = 0, behavior = 'smooth', adjustTargetY, durationMs = 320 } = options\n\n // Pick the scroller ONCE: a fixed-height `<main overflow-y-auto>` shell scrolls\n // the element, a plain document scrolls the window. The choice can't change\n // mid-tween, so resolve it up front and route every read/write through it.\n const container = getScrollableAncestor(target)\n const readCurrent = (): number => (container ? container.scrollTop : window.scrollY)\n const writeTo = (y: number): void => {\n if (container) container.scrollTop = y\n else window.scrollTo(0, y)\n }\n\n // Target is recomputed every frame: the row's absolute position can move as\n // the page reflows (a sibling drawer collapsing) and the reachable max grows\n // as the just-opened drawer expands. Clamp to the LIVE max each frame.\n const computeTarget = (): number => {\n const raw = container\n ? container.scrollTop +\n (target.getBoundingClientRect().top - container.getBoundingClientRect().top) -\n headerOffset\n : target.getBoundingClientRect().top + window.scrollY - headerOffset\n const adjusted = adjustTargetY ? adjustTargetY(raw) : raw\n const maxScroll = container\n ? Math.max(0, container.scrollHeight - container.clientHeight)\n : Math.max(0, document.documentElement.scrollHeight - window.innerHeight)\n return Math.min(Math.max(0, adjusted), maxScroll)\n }\n\n // Any prior animation loses — one page scroll at a time.\n cancelActiveScroll()\n\n const prefersReduced =\n typeof window.matchMedia === 'function' &&\n window.matchMedia('(prefers-reduced-motion: reduce)').matches\n\n // Instant paths: a single synchronous write. No tween, no anchoring race.\n if (behavior === 'instant' || behavior === 'auto' || prefersReduced) {\n writeTo(computeTarget())\n return\n }\n\n // Smooth: self-driven tween with instant per-frame writes (anchoring-proof).\n let startY: number | null = null\n let startTime = 0\n\n // Bail the moment the user takes over with a real scroll gesture — we must\n // never fight them. (Not keydown: the ticket composer auto-focuses on open,\n // and typing there should not abort the scroll.)\n const onUserGesture = () => cancelActiveScroll()\n window.addEventListener('wheel', onUserGesture, { passive: true })\n window.addEventListener('touchmove', onUserGesture, { passive: true })\n teardownActive = () => {\n window.removeEventListener('wheel', onUserGesture)\n window.removeEventListener('touchmove', onUserGesture)\n }\n\n const step = (now: number) => {\n if (startY === null) {\n startY = readCurrent()\n startTime = now\n }\n const targetY = computeTarget()\n const t = Math.min(1, (now - startTime) / durationMs)\n const y = startY + (targetY - startY) * easeOutCubic(t)\n writeTo(y)\n if (t < 1) {\n activeRaf = requestAnimationFrame(step)\n } else {\n // Final exact write in case easing left a sub-pixel gap, then teardown.\n writeTo(computeTarget())\n activeRaf = 0\n if (teardownActive) {\n teardownActive()\n teardownActive = null\n }\n }\n }\n activeRaf = requestAnimationFrame(step)\n}\n","/**\n * Shared list-API URL builder — the single un-replicable piece of the\n * chat entity-card fetch path.\n *\n * ## Why this exists\n *\n * A fetch-mode entity card (`dispatch.tsx` → `useChatCardItem`) expands a\n * compact `[card://<type>:<id>]` marker by fetching the type's public list\n * endpoint with an `?ids=` (or `?task_ids=`) filter and matching the row\n * back by id. The per-type URL SHAPE is non-obvious — `task_ids` for the\n * ClickUp-backed types, `pageSize` for blog, `&limit=N&filter=all` for the\n * programs trio, a distinct `/customer-interviews` path, etc. — and lives in\n * the hub's 12 RAG mapper closures (`lib/config/rag-mappers/*.ts`). An\n * embedder can't reverse-engineer those shapes, so a wrong/missing builder\n * resolves a null URL ⇒ `enabled:false` ⇒ the card never fetches and renders\n * nothing.\n *\n * This builder is the ONE source for those shapes. The hub's 12 mappers\n * delegate their `listApi` here (a byte-parity test guards the migration),\n * and embedders wire it once:\n *\n * endpoints.buildListUrl = (type, ids) => buildListUrl(type, ids, '/content')\n *\n * `base=''` (default) yields the hub-relative `/api/...`; `base='/content'`\n * (or any reverse-proxy prefix) yields `/content/api/...`.\n *\n * Pure + server-safe (no React, no browser APIs) so the hub's server-side\n * mappers can import it from `@flamingo-stack/openframe-frontend-core/utils`.\n */\n\n/**\n * Legacy ContentRef aliases that predate the RAG `documentType`\n * unification — direct lib/embedder callers (e.g. the chat dispatcher's\n * `endpoints.buildListUrl`) still emit `blog_post_existing`. Mirrors the\n * hub's own `LEGACY_TYPE_ALIASES` (`lib/utils/entity-list-api.ts`); both\n * entry points un-alias to the canonical `documentType` before lookup.\n */\nconst ALIASES: Record<string, string> = { blog_post_existing: 'blog_post' }\n\n/** Resolve a ContentRef rail-vocab type to its canonical `documentType`\n * (registry vocabulary) — `blog_post_existing` → `blog_post`, everything\n * else passes through. Exported so consumers comparing rail group keys to\n * registry entityTypes (e.g. the related-content rail's same-type-first\n * ordering) share THIS alias map instead of re-declaring it. */\nexport function canonicalContentRefType(contentRefType: string): string {\n return ALIASES[contentRefType] ?? contentRefType\n}\n\n/**\n * One builder per fetch-mode `contentRefType`, keyed by the canonical\n * `documentType`. Each body is copied VERBATIM from the matching hub\n * mapper's `listApi` (raw, unencoded `join(',')`; exact param names) —\n * the byte-parity test (`__tests__/list-url.test.ts`) fails if any drifts.\n *\n * An ABSENT key ⇒ `buildListUrl` returns null (no-fetch). The no-list\n * types (`github_*`, `slack_message`, financials, docs) are intentionally\n * NOT enumerated here — their absence IS the null. `marketing_campaign`\n * is handled as a literal case in `buildListUrl` (see there).\n */\nconst BUILDERS: Record<string, (ids: string[], base: string) => string> = {\n roadmap_item: (ids, b) => `${b}/api/roadmap?task_ids=${ids.join(',')}`,\n delivery_item: (ids, b) => `${b}/api/delivery?task_ids=${ids.join(',')}`,\n internal_task: (ids, b) => `${b}/api/internal-tasks?task_ids=${ids.join(',')}`,\n blog_post: (ids, b) => `${b}/api/blog/posts?ids=${ids.join(',')}&pageSize=${ids.length}`,\n webinar: (ids, b) => `${b}/api/programs/webinars?ids=${ids.join(',')}&limit=${ids.length}&filter=all`,\n podcast: (ids, b) => `${b}/api/programs/podcasts?ids=${ids.join(',')}&limit=${ids.length}&filter=all`,\n event: (ids, b) => `${b}/api/programs/events?ids=${ids.join(',')}&limit=${ids.length}&filter=all`,\n onboarding_guide: (ids, b) => `${b}/api/onboarding-guides?ids=${ids.join(',')}&limit=${ids.length}`,\n case_study: (ids, b) => `${b}/api/case-studies?ids=${ids.join(',')}&limit=${ids.length}`,\n product_release: (ids, b) => `${b}/api/releases?ids=${ids.join(',')}&limit=${ids.length}`,\n customer_interview: (ids, b) => `${b}/api/customer-interviews?ids=${ids.join(',')}&limit=${ids.length}`,\n investor_update: (ids, b) => `${b}/api/investor-updates?ids=${ids.join(',')}&limit=${ids.length}`,\n faq: (ids, b) => `${b}/api/faqs?ids=${ids.join(',')}&limit=${ids.length}`,\n}\n\n/**\n * Build a list-API URL that returns full rows for the given ids, or `null`\n * when the type has no list endpoint (the caller skips fetching rather than\n * fabricating a URL).\n *\n * buildListUrl('roadmap_item', ['a','b']) → '/api/roadmap?task_ids=a,b'\n * buildListUrl('blog_post_existing', ['a','b']) → '/api/blog/posts?ids=a,b&pageSize=2' (alias)\n * buildListUrl('roadmap_item', ['a','b'], '/content') → '/content/api/roadmap?task_ids=a,b'\n * buildListUrl('github_pr', ['a']) → null (absent key)\n *\n * `marketing_campaign` (admin-only, non-RAG) is a LITERAL case, NOT a\n * `BUILDERS` entry — a static branch keeps CodeQL able to prove no\n * user-controlled dynamic dispatch reaches the admin endpoint; embedders\n * can't hit `/api/admin` through their proxy anyway.\n */\nexport function buildListUrl(contentRefType: string, ids: string[], base = ''): string | null {\n if (ids.length === 0) return null\n const key = ALIASES[contentRefType] ?? contentRefType\n if (key === 'marketing_campaign') {\n // Keep this URL in sync with the hub's `entity-list-api.ts` buildNonRagListUrl\n // — an intentional dual literal (a static branch in each) so CodeQL can prove\n // no user-controlled dynamic dispatch reaches `/api/admin`.\n return `${base}/api/admin/marketing/campaigns?ids=${ids.join(',')}&pageSize=${ids.length}`\n }\n // `hasOwnProperty` guard so a prototype key (`constructor`, `__proto__`)\n // can't dispatch to a non-builder — absent key ⇒ null.\n const fn = Object.prototype.hasOwnProperty.call(BUILDERS, key) ? BUILDERS[key] : undefined\n return fn ? fn(ids, base) : null\n}\n","/**\n * FAQ anchor SSOTs — TWO complementary deep-link kinds on the `/faqs` page,\n * both rendered straight onto the DOM and recognised by ONE parser. Keeping\n * the format helpers + parser in one file means the page, the chat RAG\n * mapper, and any future consumer can't drift on what a hash means.\n *\n * `/faqs#faq-pricing` → category section header (jump-nav pills,\n * scroll-spy)\n * `/faqs#faq-item-42` → individual question (chat citation chips —\n * auto-expands the row + scrolls to it)\n *\n * Reserved namespaces — `faq-item-<digits>` is the item shape; everything\n * else starting with `faq-` is a section slug. `faqSectionSlug` lowercases\n * + dash-collapses + dash-trims, so a section name would only collide with\n * the item shape if it slugified to `faq-item-<digits-only>` (e.g. a\n * category called \"Item 42\") — none of the 21 production sections do, and\n * `parseFaqHash`'s regex is digits-only so future word-suffixed names like\n * \"Item Whatever\" (slugifies to `faq-item-whatever`) are also safe.\n */\n\n/** Stable, URL-safe anchor id for a category. Prefixed with `faq-` so it\n * can't collide with other in-page ids, and so a bare numeric/blank\n * section still yields a valid id. The helper assumes the caller has\n * already verified the section is a non-blank string (matches\n * `faq-section.tsx`'s `groupFaqsBySection` precondition). */\nexport function faqSectionSlug(section: string): string {\n return (\n 'faq-' +\n section\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, '-')\n .replace(/^-+|-+$/g, '')\n )\n}\n\n/** Stable anchor for an individual FAQ row. Rendered as the row container's\n * `id` attribute by `FaqAccordion`; consumed by `FaqSection` (auto-open +\n * auto-scroll on mount) AND by the hub's RAG mapper so chat citations\n * deep-link to the specific question, not just its category. The id is\n * stringified verbatim — the FAQ schema uses integer PKs so `parseFaqHash`'s\n * digits-only regex always matches a real row. */\nexport function faqItemAnchor(id: number | string): string {\n return `faq-item-${id}`\n}\n\n/** Discriminated parse of a `/faqs#…` hash. Returns null for an empty,\n * missing, or unrecognised hash so callers can early-out without\n * scattering string parsing across the file.\n *\n * parseFaqHash('#faq-item-42') → { kind: 'item', rawId: '42' }\n * parseFaqHash('#faq-pricing') → { kind: 'section', slug: 'faq-pricing' }\n * parseFaqHash('#anything-else') → null\n *\n * `rawId` is the matched digit run as a string — the caller compares it\n * to `String(item.id)` so coercion stays at the comparison site. */\nexport type FaqHashTarget =\n | { kind: 'item'; rawId: string }\n | { kind: 'section'; slug: string }\n\nconst FAQ_ITEM_HASH_RE = /^faq-item-(\\d+)$/\n\nexport function parseFaqHash(hash: string | null | undefined): FaqHashTarget | null {\n if (!hash) return null\n const trimmed = hash.replace(/^#/, '')\n if (!trimmed) return null\n const itemMatch = FAQ_ITEM_HASH_RE.exec(trimmed)\n if (itemMatch) return { kind: 'item', rawId: itemMatch[1] }\n if (trimmed.startsWith('faq-')) return { kind: 'section', slug: trimmed }\n return null\n}\n","/**\n * Content Ref Group Configuration\n *\n * Single source of truth for grouping content_refs by type. Used by both:\n * - components/related-content (detail page rail — lives in this lib)\n * - the hub's lib/utils/investor-email-utils.ts (email builder, via the\n * hub's re-export shim at lib/config/content-ref-groups.ts)\n *\n * Lives in utils/ (server-safe tsup block) so hub server-side code can\n * import it.\n *\n * Each entry carries the metadata RelatedContentSection needs to render the\n * group WITHOUT re-implementing per-type logic locally:\n * - `label`: section heading.\n * - `order`: source-stable ordering (lowest first).\n * - `layout`: 'list' (one-per-row, full-width cards) or 'grid' (responsive\n * column grid).\n * - `gridSize`: card-size variant passed to the per-type card dispatch.\n * `'lg'` matches the canonical large-card variant openframe + flamingo\n * apps already use; `'default'` is the legacy denser variant. New types\n * should pick `'lg'` whenever the card has a large variant.\n *\n * Adding a new content type = one entry here. RelatedContentSection picks up\n * the new group automatically (no hardcoded set to update). NOTE: the hub's\n * related-content SUGGESTION candidate list is DERIVED from these keys (minus\n * an explicit exclusion knob) — a new entry here becomes a suggestion\n * candidate iff it also resolves through the hub's TYPE_TO_ENTITY → RAG\n * config chain (unresolvable entries are dropped loudly at module load).\n */\n\nexport type ContentRefLayout = 'list' | 'grid'\n\n/** Subset of `EntityCardSize` (entity-card dispatch) appropriate for\n * grid/list rendering in RelatedContentSection. Duplicated as a literal\n * union here to avoid a config→component import cycle. Kept in lockstep\n * with `EntityCardSize`; widen here when a new size is added there. */\nexport type ContentRefGridSize = 'lg' | 'default' | 'sm'\n\nexport interface ContentRefGroupConfig {\n label: string\n order: number\n layout: ContentRefLayout\n gridSize: ContentRefGridSize\n}\n\nexport const CONTENT_REF_GROUPS: Record<string, ContentRefGroupConfig> = {\n investor_update: { label: 'Investor Updates', order: 1, layout: 'grid', gridSize: 'default' },\n product_release: { label: 'Product Releases', order: 2, layout: 'list', gridSize: 'lg' },\n podcast: { label: 'Podcasts', order: 3, layout: 'list', gridSize: 'default' },\n webinar: { label: 'Webinars', order: 4, layout: 'list', gridSize: 'default' },\n case_study: { label: 'Case Studies', order: 5, layout: 'grid', gridSize: 'default' },\n event: { label: 'Events', order: 6, layout: 'list', gridSize: 'default' },\n blog_post_existing: { label: 'Blog Posts', order: 7, layout: 'grid', gridSize: 'default' },\n customer_interview: { label: 'Customer Interviews', order: 8, layout: 'grid', gridSize: 'default' },\n onboarding_guide: { label: 'Onboarding Guides', order: 9, layout: 'list', gridSize: 'default' },\n what_i_shipped: { label: 'What I Shipped', order: 10, layout: 'grid', gridSize: 'default' },\n}\n\n/** Human-readable label for a content_ref `type`. Returns null when the type\n * isn't registered — caller decides the fallback. Use `getContentRefLabelOrTitleCase`\n * to get a consistent title-cased fallback across all surfaces. */\nexport function getContentRefLabel(type: string): string | null {\n return CONTENT_REF_GROUPS[type]?.label ?? null\n}\n\n/** Resolved label with a single shared fallback shape — title-cased version\n * of the raw type string for any unregistered type. Both RelatedContentSection\n * AND the investor-email builder consume this so cross-surface label rendering\n * for unregistered types is identical (no drift between \"podcast guest\" and\n * \"Podcast Guest\"). */\nexport function getContentRefLabelOrTitleCase(type: string): string {\n return (\n CONTENT_REF_GROUPS[type]?.label ??\n type.replace(/_/g, ' ').replace(/\\b\\w/g, (ch) => ch.toUpperCase())\n )\n}\n\n/** Sort a set of present content_ref types into the canonical group order\n * (registered types first in CONTENT_REF_GROUPS `order`, then unregistered\n * types appended in insertion order). Used by RelatedContentSection AND the\n * investor-email builder so cross-surface ordering stays identical. */\nexport function orderContentRefTypes(present: Iterable<string>): string[] {\n const presentSet = new Set(present)\n const registeredInOrder = Object.entries(CONTENT_REF_GROUPS)\n .sort(([, a], [, b]) => a.order - b.order)\n .map(([type]) => type)\n .filter((type) => presentSet.has(type))\n const unregistered = [...presentSet].filter((type) => !CONTENT_REF_GROUPS[type])\n return [...registeredInOrder, ...unregistered]\n}\n","/**\n * Shared fetch-URL composer for the two suggestion sections (FaqSection +\n * RelatedContentSection) — ONE owner of the entityType/entityId/count query\n * shape so the sections can never drift. Pure + server-safe.\n *\n * Byte-parity contract with the original `buildFaqsUrl`: when only\n * entityType/entityId are set, the param order is `entityType`, `entityId`;\n * with nothing set, the bare `path` is returned (no `?`).\n */\nexport interface SuggestionUrlOptions {\n /** Fetch-URL prefix for third-party embeds / reverse proxies\n * ('' = same-origin relative). */\n apiBaseUrl?: string\n /** Both required together for entity scope; partial → bare path. */\n entityType?: string\n entityId?: number | string\n /** Maps to the server's `count` param. Undefined → param not sent\n * (server default applies). */\n count?: number\n /** Extra query params appended after the shared trio (e.g. the\n * related-content `excludeTypes` deny-list). Empty/undefined values\n * are skipped. */\n extraParams?: Record<string, string | undefined>\n}\n\nexport function buildSuggestionUrl(path: string, opts: SuggestionUrlOptions = {}): string {\n const qs = new URLSearchParams()\n const { entityType, entityId } = opts\n if (entityType && entityId !== undefined && entityId !== null && entityId !== '') {\n qs.set('entityType', entityType)\n qs.set('entityId', String(entityId))\n }\n if (opts.count !== undefined) qs.set('count', String(opts.count))\n for (const [key, value] of Object.entries(opts.extraParams ?? {})) {\n if (value !== undefined && value !== '') qs.set(key, value)\n }\n const query = qs.toString()\n return `${opts.apiBaseUrl ?? ''}${path}${query ? `?${query}` : ''}`\n}\n","/**\n * Pure helpers for doc-source tree navigation — shared by client hook and RSC SSR.\n * README convention (folder-index file) is configurable but defaults to 'README.md'.\n */\n\nimport type { DocNode } from '../types/doc-source'\n\n/**\n * Canonical folder-index filename. Single SSOT consumed by `doc-tree-nav`\n * pure helpers, `useDocumentTree` (default config), `DocSeoContent` (fallback\n * fetch path), and `multi-level-navigation`'s visual-selection logic. Stays\n * in canonical case here; consumers that do case-insensitive comparison\n * (the navigation) lowercase locally.\n */\nexport const DEFAULT_FOLDER_INDEX_FILE = 'README.md'\n\n/** @deprecated Re-exported under the new name; remove after consumers migrate. */\nconst DEFAULT_FOLDER_INDEX = DEFAULT_FOLDER_INDEX_FILE\n\n/**\n * Strip the folder-index filename from a path, returning the folder path.\n * For default 'README.md': 'folder/README.md' → 'folder', 'README.md' → '', 'folder/file.md' → 'folder/file.md'\n */\nexport function stripFolderIndexFromPath(\n path: string,\n folderIndexFile: string = DEFAULT_FOLDER_INDEX,\n): string {\n const suffix = `/${folderIndexFile}`\n if (path.endsWith(suffix)) return path.slice(0, -suffix.length)\n if (path === folderIndexFile) return ''\n return path\n}\n\nexport function findDocNodeByPath(path: string, nodes: DocNode[]): DocNode | null {\n for (const node of nodes) {\n if (node.path === path) return node\n if (node.children) {\n const found = findDocNodeByPath(path, node.children)\n if (found) return found\n }\n }\n return null\n}\n\n/**\n * Given a node path like \"folder1/subfolder2\", return DOM-style node IDs for each segment\n * (accordion sidebar — matches use-document-tree).\n */\nexport function getDocAncestorNodeIds(nodePath: string): string[] {\n const parts = nodePath.split('/')\n const ids: string[] = []\n let current = ''\n for (const part of parts) {\n current = current ? `${current}-${part}` : part\n ids.push(current.toLowerCase())\n }\n return ids\n}\n\n/**\n * Resolve which storage path to load (matches use-document-tree content effect).\n */\nexport function resolveContentFetchPath(\n selectedPath: string,\n structure: DocNode[],\n folderIndexFile: string = DEFAULT_FOLDER_INDEX,\n): string | null {\n if (selectedPath === '') {\n return folderIndexFile\n }\n const node = findDocNodeByPath(selectedPath, structure)\n if (node && node.type === 'folder' && !node.hasReadme) {\n return null\n }\n let pathToFetch = selectedPath\n if (node && node.type === 'folder' && node.hasReadme) {\n pathToFetch = `${selectedPath}/${folderIndexFile}`\n }\n return pathToFetch\n}\n\nexport function getInitialExpandedNodeIds(cleanInitialPath: string): string[] {\n if (!cleanInitialPath) return []\n const pathForExpansion = cleanInitialPath.includes('.')\n ? cleanInitialPath.substring(0, cleanInitialPath.lastIndexOf('/'))\n : cleanInitialPath\n if (!pathForExpansion) return []\n return getDocAncestorNodeIds(pathForExpansion)\n}\n\n/**\n * When a doc source's root has no top-level folder-index file, navigate to the first\n * folder that does. Matches the historical knowledge-base default.\n */\nexport function getDocSourceDefaultPath(\n structure: DocNode[],\n folderIndexFile: string = DEFAULT_FOLDER_INDEX,\n): string | null {\n if (!structure.length) return null\n const hasRootIndex = structure.some(\n (node) => node.type === 'file' && node.path === folderIndexFile\n )\n if (hasRootIndex) return null\n // Scan ALL root nodes for the first folder with a README — not just\n // structure[0]. Otherwise a leading non-folder root (file / folder without\n // README) silently returns null even when a later root folder has one.\n const firstFolderWithReadme = structure.find(\n (node) => node.type === 'folder' && node.hasReadme && !!node.path,\n )\n return firstFolderWithReadme?.path ?? null\n}\n","/**\n * Shared document path utilities.\n * Single source of truth for path/slug operations used by all doc-source viewers\n * (knowledge base, data room, future).\n */\n\nimport { DEFAULT_FOLDER_INDEX_FILE } from './doc-tree-nav'\n\n/**\n * Convert a document name to a URL-safe path slug.\n * Used when creating, moving, or renaming documents.\n * e.g., \"Investor Thesis\" → \"investor-thesis\"\n */\nexport function toDocSlug(name: string): string {\n return name.trim().toLowerCase().replace(/\\s+/g, '-')\n}\n\n/**\n * Convert a full document path to a DOM-safe node ID.\n * Used for tree node IDs and sidebar expand/collapse state.\n * e.g., \"legal/cap-table/safe.md\" → \"legal-cap-table-safe.md\"\n */\nexport function pathToNodeId(path: string): string {\n return path.replace(/[\\/\\s]/g, '-').toLowerCase()\n}\n\n/**\n * Normalize a raw URL path from a route handler.\n * Strips folder-index suffixes (default README.md) and lowercases segments.\n * Used by both data-room and knowledge-base [...path] route handlers.\n */\nexport function normalizeDocPath(\n segments: string[],\n folderIndexFile: string = DEFAULT_FOLDER_INDEX_FILE,\n): string {\n // Lowercase BOTH sides BEFORE comparison so paths from URL segments\n // (`readme.md`) and the canonical folder-index constant (`README.md`)\n // strip uniformly. Lowercasing only at the end (the previous shape) left\n // lowercase URL inputs unstripped → bogus content lookups.\n const lowerPath = (segments?.join('/') || '').toLowerCase()\n const lowerIndex = folderIndexFile.toLowerCase()\n\n const suffix = `/${lowerIndex}`\n if (lowerPath.endsWith(suffix)) return lowerPath.slice(0, -suffix.length)\n if (lowerPath === lowerIndex) return ''\n return lowerPath\n}\n","// =============================================================================\n// Shared Document Tree Builder\n// =============================================================================\n// Generic 3-pass algorithm for building hierarchical trees from flat records.\n// Used by all doc-source DALs (openframe-docs, data-room-docs, future).\n\n/**\n * Base interface for tree nodes. Consumers extend this with additional fields.\n */\nexport interface TreeNodeBase {\n id: string\n name: string\n path: string\n type: 'file' | 'folder'\n sortOrder?: number\n children?: TreeNodeBase[]\n}\n\n/**\n * Format a document name for display: remove .md extension and capitalize.\n */\nexport function formatDocName(name: string): string {\n let displayName = name\n if (displayName.endsWith('.md')) {\n displayName = displayName.slice(0, -3)\n }\n if (displayName.toLowerCase() === 'readme') {\n return 'README'\n }\n return displayName.charAt(0).toUpperCase() + displayName.slice(1).replace(/-/g, ' ')\n}\n\n/**\n * Sort children: README first, then folders, then files — each group by sort_order then alphabetical.\n * Recurses into folder children to ensure consistent ordering at all levels.\n */\nexport function sortTreeChildren<TNode extends TreeNodeBase>(nodes: TNode[]): TNode[] {\n nodes.sort((a, b) => {\n const aIsReadme = a.name.toLowerCase() === 'readme' || a.name.toLowerCase() === 'readme.md'\n const bIsReadme = b.name.toLowerCase() === 'readme' || b.name.toLowerCase() === 'readme.md'\n if (aIsReadme && !bIsReadme) return -1\n if (!aIsReadme && bIsReadme) return 1\n\n if (a.type === 'folder' && b.type !== 'folder') return -1\n if (a.type !== 'folder' && b.type === 'folder') return 1\n\n const aOrder = a.sortOrder ?? Infinity\n const bOrder = b.sortOrder ?? Infinity\n if (aOrder !== bOrder) return aOrder - bOrder\n\n return a.name.localeCompare(b.name, undefined, { sensitivity: 'base' })\n })\n\n for (const node of nodes) {\n if (node.type === 'folder' && node.children) {\n node.children = sortTreeChildren(node.children as TNode[])\n }\n }\n\n return nodes\n}\n\n/**\n * Build a hierarchical document tree from a flat list of records.\n *\n * 3-pass algorithm:\n * 1. Create node map from flat records using the provided `mapFn`\n * 2. Build parent-child hierarchy using `getParentPath`\n * 3. Sort children (folders first, README, then others)\n */\nexport function buildDocumentTree<TNode extends TreeNodeBase, TDoc>(\n docs: TDoc[],\n mapFn: (doc: TDoc) => TNode,\n getParentPath: (doc: TDoc) => string | null,\n getPath: (doc: TDoc) => string\n): TNode[] {\n const nodeMap = new Map<string, TNode>()\n const rootNodes: TNode[] = []\n\n for (const doc of docs) {\n const node = mapFn(doc)\n nodeMap.set(getPath(doc), node)\n }\n\n for (const doc of docs) {\n const node = nodeMap.get(getPath(doc))!\n let resolvedParentPath = getParentPath(doc)\n\n if (!resolvedParentPath) {\n const nodePath = getPath(doc)\n const lastSlash = nodePath.lastIndexOf('/')\n if (lastSlash > 0) {\n resolvedParentPath = nodePath.substring(0, lastSlash)\n }\n }\n\n if (resolvedParentPath) {\n const parent = nodeMap.get(resolvedParentPath)\n if (parent) {\n // Lazily initialize `children` so callers whose `mapFn` doesn't\n // pre-seed it still get a proper tree (previously a missing\n // `parent.children` silently flattened the child to the root).\n if (!parent.children) parent.children = []\n ;(parent.children as TNode[]).push(node)\n } else {\n rootNodes.push(node)\n }\n } else {\n rootNodes.push(node)\n }\n }\n\n return sortTreeChildren(rootNodes)\n}\n","/**\n * Convert markdown source to plain text. Used by both the SEO server component\n * (article-body echo in JSON-LD) and chat entity-card preview (clamped previews\n * inside chat cards).\n *\n * The two consumers differ only in pre/post processing — `mode: 'article'`\n * strips code blocks + images + headings + list bullets and preserves paragraph\n * breaks; `mode: 'preview'` collapses everything to a single line. Both share\n * the same inline-marker strip (link / bold / italic / inline-code).\n *\n * All regex patterns are linear (negated character classes, non-greedy with\n * bounded targets) — no nested quantifiers, no catastrophic backtracking on\n * pathological input.\n */\n\nexport interface MarkdownToPlainOptions {\n /**\n * - `'article'` (default): preserve paragraph structure (`\\n\\n` as separator),\n * strip code blocks + images + headings + list bullets. Used for the\n * article-body echo on SEO surfaces.\n * - `'preview'`: collapse all whitespace to single spaces; result is a single\n * line. Used for chat-card previews / OG-card snippets where line breaks\n * would wrap awkwardly.\n */\n mode?: 'article' | 'preview'\n /** Optional max-character cap. Truncates with no ellipsis (caller-decided). */\n maxChars?: number\n}\n\n/** Strip inline markdown markers (bold, italic, inline-code, links). Shared\n * by `extractSections`'s `stripFormattingMarkers` and `markdownToPlainText`. */\nexport function stripInlineMarkdown(s: string): string {\n return s\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, '$1') // [text](url) → text\n .replace(/\\*\\*([^*]+)\\*\\*/g, '$1') // **bold** → bold\n .replace(/\\*([^*]+)\\*/g, '$1') // *italic* → italic\n .replace(/`([^`]+)`/g, '$1') // `inline code` → inline code\n}\n\nexport function markdownToPlainText(\n markdown: string,\n options: MarkdownToPlainOptions = {},\n): string {\n const { mode = 'article', maxChars } = options\n\n let out = markdown\n // Fenced code blocks — must run BEFORE inline strippers (otherwise the\n // backtick-grave strip below grazes them).\n .replace(/```[\\s\\S]*?```/g, '')\n // Images — `![alt](src)` → empty (we drop them entirely in plain text).\n .replace(/!\\[[^\\]]*\\]\\([^)]+\\)/g, '')\n\n out = stripInlineMarkdown(out)\n // Heading markers (any depth).\n .replace(/^#+\\s*/gm, '')\n // List bullets at start-of-line.\n .replace(/^\\s*[-*+]\\s+/gm, '')\n\n if (mode === 'preview') {\n out = out.replace(/\\s+/g, ' ').trim()\n } else {\n out = out.replace(/\\n{3,}/g, '\\n\\n').trim()\n }\n\n if (maxChars != null && out.length > maxChars) {\n out = out.slice(0, maxChars)\n }\n return out\n}\n","/**\n * Shared markdown section extractor\n * Extracts heading sections from markdown content for navigation.\n */\n\nimport { stripInlineMarkdown } from './markdown-to-plain'\n\nexport interface MarkdownSection {\n id: string\n title: string\n level: number\n}\n\nexport interface ExtractSectionsOptions {\n /** Maximum heading level to include (default: 2 = H1-H2) */\n maxLevel?: number\n /** Remove emoji characters from IDs (default: true) */\n removeEmojis?: boolean\n /** Handle duplicate IDs by appending a counter suffix (default: true) */\n handleDuplicateIds?: boolean\n /** Strip bold, italic, and inline code markers from titles (default: true) */\n stripFormattingMarkers?: boolean\n /** Skip code blocks and YAML frontmatter (default: true) */\n skipCodeAndYamlBlocks?: boolean\n}\n\nconst DEFAULT_OPTIONS: Required<ExtractSectionsOptions> = {\n maxLevel: 2,\n removeEmojis: true,\n handleDuplicateIds: true,\n stripFormattingMarkers: true,\n skipCodeAndYamlBlocks: true,\n}\n\nexport function extractSections(\n markdown: string,\n options: ExtractSectionsOptions = {},\n): MarkdownSection[] {\n const opts = { ...DEFAULT_OPTIONS, ...options }\n const sections: MarkdownSection[] = []\n const lines = markdown.split('\\n')\n const idCounts: Record<string, number> = {}\n\n let inCodeBlock = false\n let inYamlBlock = false\n\n for (const line of lines) {\n if (opts.skipCodeAndYamlBlocks) {\n if (line.startsWith('```')) {\n inCodeBlock = !inCodeBlock\n continue\n }\n if (line === '---') {\n inYamlBlock = !inYamlBlock\n continue\n }\n if (inCodeBlock || inYamlBlock) continue\n }\n\n const pattern = new RegExp(`^(#{1,${opts.maxLevel}})\\\\s+(.+)`)\n const match = line.match(pattern)\n if (!match) continue\n\n const level = match[1].length\n if (level > opts.maxLevel) continue\n\n let title = match[2].trim()\n\n if (opts.stripFormattingMarkers) {\n title = stripInlineMarkdown(title).trim()\n }\n\n let baseId = title\n\n if (opts.removeEmojis) {\n baseId = baseId\n .replace(\n /[\\u{1F300}-\\u{1F9FF}]|[\\u{2600}-\\u{26FF}]|[\\u{2700}-\\u{27BF}]/gu,\n '',\n )\n .trim()\n }\n\n baseId = baseId\n .toLowerCase()\n .replace(/[^\\w\\s-]/g, '')\n .replace(/\\s+/g, '-')\n .replace(/^-+|-+$/g, '')\n\n const cleanId = baseId || `section-${sections.length + 1}`\n\n let id = cleanId\n if (opts.handleDuplicateIds) {\n if (idCounts[cleanId]) {\n idCounts[cleanId]++\n id = `${cleanId}-${idCounts[cleanId]}`\n } else {\n idCounts[cleanId] = 1\n }\n }\n\n sections.push({ id, title, level })\n }\n\n return sections\n}\n","/**\n * Shared utilities for converting external service URLs to embeddable formats.\n * Used by lib's embed components and the hub's admin document editor.\n */\n\nexport function toGoogleSheetsEmbedUrl(url: string): string {\n if (url.includes('/htmlembed')) return url\n\n const match = url.match(/\\/spreadsheets\\/d\\/([a-zA-Z0-9-_]+)/)\n if (!match) return url\n\n const gidMatch = url.match(/[#?&]gid=(\\d+)/)\n const gid = gidMatch ? gidMatch[1] : '0'\n\n return `https://docs.google.com/spreadsheets/d/${match[1]}/htmlembed?widget=true&chrome=false&headers=false&gid=${gid}`\n}\n\nexport function toGoogleSheetsOriginalUrl(url: string): string {\n const match = url.match(/\\/spreadsheets\\/d\\/([a-zA-Z0-9-_]+)/)\n if (!match) return url\n\n const gidMatch = url.match(/[#?&]gid=(\\d+)/)\n const gid = gidMatch ? gidMatch[1] : '0'\n\n return `https://docs.google.com/spreadsheets/d/${match[1]}/edit#gid=${gid}`\n}\n\n/**\n * Convert a Figma URL to an embeddable URL.\n * Slides/deck URLs map to `deck` (present) by default; `slidesView: 'browse'` switches to `slides` (browse).\n */\nexport function toFigmaEmbedUrl(\n url: string,\n opts?: { slidesView?: 'present' | 'browse' }\n): string {\n if (url.includes('embed.figma.com')) return url\n if (url.includes('figma.com/embed')) return url\n\n const match = url.match(\n /figma\\.com\\/(design|file|proto|board|slides|deck)\\/([a-zA-Z0-9]+)(?:\\/([^?]*))?(\\?.*)?$/\n )\n\n if (match) {\n const [, urlType, fileKey, titleSlug, queryString] = match\n const isSlides = urlType === 'slides' || urlType === 'deck'\n const embedType =\n urlType === 'proto' ? 'proto'\n : isSlides ? (opts?.slidesView === 'browse' ? 'slides' : 'deck')\n : 'design'\n const pathSuffix = titleSlug ? `/${titleSlug}` : ''\n\n const params = new URLSearchParams(queryString?.replace(/^\\?/, '') || '')\n if (!params.has('embed-host')) {\n params.set('embed-host', 'flamingo')\n }\n const clientId = process.env.NEXT_PUBLIC_FIGMA_CLIENT_ID\n if (clientId && !params.has('client-id')) {\n params.set('client-id', clientId)\n }\n\n return `https://embed.figma.com/${embedType}/${fileKey}${pathSuffix}?${params.toString()}`\n }\n\n return `https://www.figma.com/embed?embed-host=flamingo&url=${encodeURIComponent(url)}`\n}\n\nexport function isFigmaSlidesUrl(url: string): boolean {\n if (!url) return false\n return /(?:www\\.|embed\\.)?figma\\.com\\/(?:slides|deck)\\/[a-zA-Z0-9]+/.test(url)\n}\n\nexport function toFigmaOriginalUrl(url: string): string {\n if (url.includes('embed.figma.com')) {\n return url.replace('embed.figma.com', 'www.figma.com').replace(/\\?.*$/, '')\n }\n return url.replace(/\\?.*$/, '')\n}\n","// Utils exports - client-side only\nexport { cn } from './cn'\n// Platform→domain SSOT lives in `../platform-domains` (single definition). Re-exported\n// here so existing `/utils` callers keep working; the new resolver/helpers\n// (getPlatformByHostname/hostOf/expandWwwApex/…) are exposed via the `/platform-domains`\n// subpath ONLY (one import surface for the new API).\nexport { getPlatformProductionUrl, getAllPlatformBaseDomains } from '../platform-domains'\n// Number / currency / byte / date formatters live in `./format` (single\n// source of truth). Re-exported here so existing callers that pull from\n// the barrel keep working without changing imports.\nexport { formatDate, formatNumber, formatPrice, formatBytes } from './format'\n// SVG path constants — re-exported here (server-safe) because icons-v2 has \"use client\"\nexport { PLAY_ICON_PATH } from '../components/icons-v2-generated/media-playback/play-icon'\nexport {\n getPlatformAccentColor,\n getCurrentPlatform,\n type ColorCategory,\n HEX_PATTERN,\n // Custom-color helpers so consumers (e.g. the chat client's accent theming)\n // reuse the design-system math instead of re-implementing darken/hex utils.\n getReadableTextColor,\n hexToRgb,\n rgbToHex,\n deriveHoverColor,\n deriveActiveColor,\n} from './ods-color-utils'\nexport { delay, generateRandomString, truncateString, deepClone, getSlackCommunityJoinUrl, serializeJsonLd } from './common'\nexport { getBaseUrl } from '../utils/cn'\n\nexport * from './platform-config'\nexport * from './os-platforms'\nexport * from './access-code-client'\n// Validation utilities\nexport * from './validation-utils'\n// Note: format and date-utils are imported via cn.ts to avoid duplicates\n// AI confidence utilities\nexport * from './confidence-helpers'\n// Release date formatting utilities\nexport * from './date-formatters'\n// Product-release card helpers (lifted from the hub so every embedder gets the\n// rich card metadata): badge-color mapping + cover-image fallback resolution.\nexport * from './release-badge'\nexport * from './release-cover'\n// Dev-center URL param keys — the ONE source for the `?search=` / `?status=` / … keys the\n// chrome registry writes and the list views read; re-exported so embedders (and the hub's\n// dev-section-url helper) build deep-links with the same keys instead of a bare literal.\nexport { DEV_SECTION_PARAM_KEYS } from './dev-sections/dev-section-param-keys'\n// Dynamic icon registry — single source of truth lives at\n// components/chat/utils/icon-registry. Re-exported here so existing\n// `@flamingo-stack/openframe-frontend-core/utils` callers (hub admin\n// social-account screens) keep working without changing their import path.\nexport {\n getDynamicIcon,\n type DynamicIconSize,\n ICON_REGISTRY,\n getIconComponent,\n normalizeIconKey,\n} from '../components/chat/utils/icon-registry'\nexport { getPlatformIconComponent as getPlatformLogo } from './platform-config'\n// Tool type utilities\nexport * from './tool-utils'\n// Shell type utilities\nexport * from './shell-utils'\n// OS type utilities\nexport * from './os-utils'\n// Phone utilities\nexport * from './country-phone-utils'\n// Generic domain detection\nexport * from './generic-domain-utils'\n\n// Color analysis (canvas-based image color extraction)\nexport * from './color-analysis'\n\n// Image-proxy URL builder (pure, runtime-configurable)\nexport {\n type GetProxiedImageUrlOptions,\n getProxiedImageUrl,\n urlPathLooksLikeSvg,\n shouldProxyImage,\n generateImageSizes,\n} from './image-proxy'\n\n// Number / byte / duration / time / UTC date / initials / metric /\n// trend / range / text helpers — single canonical home for every\n// generic formatter consumed across the lib AND the hub. Hub keeps\n// only vendor-specific helpers (formatClassification / formatPricingModel\n// in `lib/utils/vendor-text.ts`).\nexport {\n formatLargeNumber,\n formatAbbreviatedNumber,\n nameInitials,\n getFirstLastInitials,\n formatDurationMMSS,\n formatDurationCompact,\n formatTimeWithTimezone,\n formatDurationFromRange,\n type FormatDateUTCOptions,\n formatDateUTC,\n formatEntryMonthUTC,\n formatCurrency,\n formatPercent,\n formatWholeDollars,\n formatLegalDate,\n formatBytesShort,\n formatFileSize,\n formatDateTimeAt,\n formatDurationFromMs,\n type MetricFormat,\n type TrendPolarity,\n formatCompactMetric,\n getTrendColors,\n formatDateRange,\n formatDuration,\n formatUnderscoreText,\n stripHtml,\n formatBioText,\n formatClassification,\n formatPricingModel,\n} from './format'\n\n// Date / time helpers (relative, absolute, range checks, UTC timestamp)\nexport {\n formatRelativeTime,\n formatAbsoluteDate,\n formatDateTime,\n getDetailedTimeDifference,\n isToday,\n isWithinMinutes,\n createUTCTimestamp,\n formatTicketRelativeTime,\n formatTicketFullTimestamp,\n} from './date-utils'\n\n// Chat source-icons + labels (server-safe — lives here in src/utils/\n// instead of src/components/chat/utils/ so server-side hub callers like\n// doc-chat-utils can use them without tripping the 'use client' boundary)\nexport {\n SOURCE_ICON_NAMES,\n getSourceIconName,\n SOURCE_LABELS_BY_TABLE,\n getSourceLabel,\n} from './source-icons'\n\n// Embed-surface auth — generic across chat AND ticket center (and any\n// future embedded React component that needs to identify as the proxied\n// customer). Wire headers / env vars / storage key are unchanged\n// (`X-Chat-*`, `CHAT_PROXY_SECRET`, `chat.proxy-auth.v1`) — those are\n// server / deployment contracts. The CLIENT-side rename frees non-chat\n// surfaces from importing a chat-prefixed symbol. Old chat-prefixed\n// aliases are kept as @deprecated re-exports at\n// `components/chat/utils/chat-authed-fetch.ts` + `chat-proxy-auth-storage.ts`.\nexport {\n embedAuthedFetch,\n setEmbedAuthAdapter,\n type EmbedAuthAdapter,\n} from './embed-authed-fetch'\nexport {\n type EmbedProxyAuth,\n getEmbedProxyAuth,\n setEmbedProxyAuth,\n clearEmbedProxyAuth,\n getPersistedProxyEmail,\n applyProxyAuth,\n} from './embed-proxy-auth-storage'\n\n// SSE leading-frame reader for the chat-agent `confirm-tool` route.\n// Used by the ticket center to drain the single `decision_resolved`\n// frame the server emits when `messages: []` is in the request body.\nexport {\n readLeadingDecisionFrame,\n type DecisionResolvedFrame,\n} from './sse-decision-frame'\n\n// Pure text/wire helpers that originated under components/chat/utils/\n// but are needed by SERVER-SIDE hub callers (doc-chat-utils,\n// hubspot-files-utils, rag-mappers). Re-exporting through utils/ keeps\n// them out of the 'use client' bundle. The chat-side re-export chain\n// stays intact for client consumers via components/chat barrel.\nexport {\n escapeMarkdownInline,\n type ChatAttachment,\n buildChatAttachmentViewUrl,\n formatChatAttachmentMarkdownForBubble,\n CHAT_ATTACHMENT_VIEW_URL_PREFIX,\n CHAT_ATTACHMENT_VIEW_TOKEN_QUERY_PARAM,\n ANTHROPIC_SUPPORTED_IMAGE_MIME,\n CHAT_ATTACHMENT_VIEW_URL_PREFIX_REGEX_ESCAPED,\n CHAT_ATTACHMENT_MARKDOWN_PATTERN,\n stripChatAttachmentMarkdown,\n} from '../components/chat/utils/chat-attachment-markdown'\nexport {\n AUTO_CONTINUATION_DIRECTIVE_PREFIX,\n buildAutoContinuationDirective,\n type BuildAutoContinuationOptions,\n} from '../components/chat/utils/auto-continuation-directive'\nexport { flattenAssistantContent } from '../components/chat/utils/flatten-assistant-content'\nexport {\n SCROLL_ANCHOR,\n type ScrollAnchor,\n SCROLL_ANCHOR_WIRE_KEY,\n parseScrollAnchor,\n} from '../components/chat/utils/scroll-anchor'\nexport {\n type WireCommandOverride,\n parseWireCommandOverride,\n sanitizeTitleForChat,\n formatSingularLookupInvocation,\n type CommandOverride,\n extractEntityIdFilter,\n buildDiscussAddendum,\n} from '../components/chat/utils/slash-dispatch-utils'\n\n// ClickUp taxonomy constants + label resolver — server-safe so the\n// delivery aggregator + sync engine can read CUSTOM_ITEM_ID.BUG/.REQUEST\n// without going through the 'use client' chat barrel (which erases the\n// values to undefined when imported from server code).\nexport { CUSTOM_ITEM_ID, getTaskTypeLabel } from '../components/chat/utils/clickup-task-type-utils'\n\n// Status color scheme + clickup URL helpers (pure, server-safe)\nexport { type ColorScheme, getStatusColorScheme } from '../components/chat/utils/agent-status-message'\nexport { clickupTaskUrl } from '../components/chat/utils/external-app-urls'\n\n// Cross-origin URL detection (pure — used by server-side rag-mappers\n// + content-url-builder for cross-origin same-tab decisions)\nexport { isCrossOriginUrl } from '../components/chat/utils/is-cross-origin-url'\n\n// React-version-aware `fetchpriority` prop builder — spread into `<img>`\n// / `<iframe>` so the rendered DOM attribute is correct under React 18\n// (lowercase) AND React 19 (camelCase) without console warnings.\nexport { fetchPriorityProp, type FetchPriorityValue } from './fetch-priority'\n\n// Dev-center section registry (Roadmap / Delivery / Releases / Onboarding) —\n// server-safe (no JSX, no contexts/* imports); imported by route-page\n// `metadata` exports + the shared `<DevSectionView>` chrome.\nexport * from './dev-sections'\n\n// Canonical \"smooth scroll element into view with sticky-chrome\n// offset\" helper. Single source of truth across the lib + hub for\n// pre-computed-target scrolling — see `scroll-into-view.ts` for the\n// rationale (TL;DR: window.scrollTo({top, behavior:'smooth'}) with a\n// pre-computed pixel value avoids the mid-animation jitter that\n// `element.scrollIntoView()` produces when layout shifts during the\n// scroll).\nexport {\n type ScrollElementIntoViewOptions,\n scrollElementIntoView,\n} from './scroll-into-view'\n\n// Shared list-API URL builder — the single source for the per-type chat\n// entity-card fetch shapes. The hub's 12 RAG mapper `listApi` closures\n// delegate here (byte-parity test guards the migration); embedders wire\n// `endpoints.buildListUrl = (t, ids) => buildListUrl(t, ids, '/content')`.\n// Pure + server-safe (the hub imports it server-side from this barrel).\nexport { buildListUrl, canonicalContentRefType } from './list-url'\n\n// FAQ anchor SSOTs — section (`faq-<slug>`) AND item (`faq-item-<id>`)\n// formats plus the parser, all rendered by `FaqSection`/`FaqAccordion`\n// and recognised by the hub's RAG mapper. One algo per kind, one parser,\n// zero drift across page + chat + future consumers.\nexport { faqSectionSlug, faqItemAnchor, parseFaqHash } from './faq-anchor'\nexport type { FaqHashTarget } from './faq-anchor'\n\n// Content-ref group registry (labels/order/layout per rail type) + list-API\n// response normalizers + the shared suggestion-fetch URL composer — all\n// pure + server-safe; the hub re-exports these from its config/util shims.\nexport {\n CONTENT_REF_GROUPS,\n getContentRefLabel,\n getContentRefLabelOrTitleCase,\n orderContentRefTypes,\n type ContentRefGroupConfig,\n type ContentRefLayout,\n type ContentRefGridSize,\n} from './content-ref-groups'\nexport { extractItems, extractItemId } from './extract-items'\nexport { buildSuggestionUrl, type SuggestionUrlOptions } from './suggestion-url'\n\n// Embedder-configurable content-URL composer for the existing\n// `runtime.composeContentUrl` seam — relative href for host-served types,\n// hub origin for the rest. Pure + server-safe.\nexport {\n DEFAULT_CONTENT_SUFFIXES,\n makeComposeContentUrl,\n buildDefaultHref,\n type ContentHrefOptions,\n type ComposeContentUrl,\n} from './content-href'\n\n// Invisible bot-protection signals (honeypot + timing) — pure + server-safe so\n// the hub's per-route `verifyHuman` gate imports the SAME `evaluateHumanitySignals`\n// decision fn the lib forms feed. Also exported via the granular subpath\n// `./utils/humanity-signals` for server-only consumers.\nexport * from './humanity-signals'\n\n// Doc-source viewer utilities (path parsing, tree building, section extraction,\n// embed-URL conversion) — single home for all doc-viewer pure helpers across\n// hub + lib consumers (knowledge-base, data-room, and future sources).\nexport * from './doc-path-utils'\nexport * from './doc-tree-nav'\nexport * from './tree-builder'\nexport * from './markdown-section-extractor'\nexport * from './markdown-to-plain'\nexport * from './embed-url-converters'\n","\"use client\"\n\nimport React, { useEffect, useState, useCallback, useRef } from 'react'\nimport { cn } from '../../utils'\nimport { scrollElementIntoView } from '../../utils/scroll-into-view'\n\nexport interface StickyNavSection {\n id: string\n label: string\n}\n\ninterface StickySectionNavProps {\n sections: StickyNavSection[]\n activeSection: string\n onSectionClick: (sectionId: string) => void\n className?: string\n ribbonPosition?: 'left' | 'right'\n ribbonColor?: string\n}\n\n/**\n * Reusable sticky navigation component for section-based navigation\n * Used in vendor detail pages, knowledge base, documentation, etc.\n */\nexport function StickySectionNav({\n sections,\n activeSection,\n onSectionClick,\n className,\n ribbonPosition = 'left',\n ribbonColor = '#FFC008'\n}: StickySectionNavProps) {\n const navHeight = sections.length * 40 // 40px per item (h-10)\n\n return (\n <nav className={cn(\"bg-ods-bg relative\", className)}>\n {/* Background gray vertical line for all nav items */}\n <div\n className=\"absolute bg-ods-border\"\n style={{\n width: '1px',\n height: `${navHeight}px`,\n [ribbonPosition === 'left' ? 'left' : 'right']: '-2px',\n top: '0px'\n }}\n />\n\n {sections.map((section) => (\n <div\n key={section.id}\n className=\"relative w-full h-10 transition-all duration-200 flex items-stretch\"\n >\n {/* Yellow ribbon for active state */}\n {activeSection === section.id && (\n <div\n className=\"absolute z-10 transition-all duration-200\"\n style={{\n backgroundColor: ribbonColor,\n width: '4px',\n height: '24px',\n [ribbonPosition === 'left' ? 'left' : 'right']: '-2px',\n top: '8px',\n borderRadius: '2px'\n }}\n />\n )}\n\n {/* Navigation button */}\n <button\n onClick={() => onSectionClick(section.id)}\n className=\"flex-1 flex items-center gap-2 px-3 py-2 cursor-pointer relative\"\n >\n <span className={cn(\n \"text-left font-['DM_Sans'] text-[14px] font-medium tracking-[-0.02em] leading-[1.43em] transition-all duration-200\",\n activeSection === section.id\n ? \"text-ods-text-primary\"\n : \"text-ods-text-secondary hover:text-ods-text-primary\"\n )}>\n {section.label}\n </span>\n </button>\n </div>\n ))}\n </nav>\n )\n}\n\n/**\n * SIMPLEST POSSIBLE IMPLEMENTATION - Just make it work\n */\nexport function useSectionNavigation(\n sections: { id: string; ref: React.RefObject<HTMLElement> }[],\n options?: {\n offset?: number\n }\n) {\n const [activeSection, setActiveSection] = useState(sections[0]?.id || '')\n const isScrollingFromClick = useRef(false)\n const { offset = 100 } = options || {}\n\n // Handle click - scroll to the element via the canonical helper.\n // The `offset` prop maps to `headerOffset` (sticky chrome above the\n // section nav); same smooth-scroll mechanics every other anchor\n // surface in the app uses.\n const handleSectionClick = useCallback((sectionId: string) => {\n const targetElement = document.getElementById(sectionId)\n if (!targetElement) return\n\n // Prevent scroll spy while we're scrolling\n isScrollingFromClick.current = true\n setActiveSection(sectionId)\n\n scrollElementIntoView(targetElement, { headerOffset: offset })\n\n // Allow scroll spy again after scroll completes\n setTimeout(() => {\n isScrollingFromClick.current = false\n }, 500)\n }, [offset])\n\n // Make sure elements have IDs\n useEffect(() => {\n sections.forEach(section => {\n if (section.ref.current && !section.ref.current.id) {\n section.ref.current.id = section.id\n }\n })\n }, [sections])\n\n // Simple scroll spy\n useEffect(() => {\n const handleScroll = () => {\n if (isScrollingFromClick.current) return\n\n const scrollPosition = window.scrollY + offset + 50\n\n // Find which section we're in\n let currentSection = sections[0]?.id || ''\n \n for (let i = sections.length - 1; i >= 0; i--) {\n const element = document.getElementById(sections[i].id)\n if (element && scrollPosition >= element.offsetTop) {\n currentSection = sections[i].id\n break\n }\n }\n\n setActiveSection(currentSection)\n }\n\n // Throttle the scroll handler\n let scrollTimer: NodeJS.Timeout\n const throttledScroll = () => {\n clearTimeout(scrollTimer)\n scrollTimer = setTimeout(handleScroll, 100)\n }\n\n window.addEventListener('scroll', throttledScroll)\n handleScroll() // Check initial position\n\n return () => {\n window.removeEventListener('scroll', throttledScroll)\n clearTimeout(scrollTimer)\n }\n }, [sections, offset])\n\n return {\n activeSection,\n handleSectionClick\n }\n}","\"use client\"\n\nimport { ChevronRight, ChevronDown, ChevronUp, FileText, Folder, FolderOpen } from 'lucide-react'\nimport { cn } from '../../utils/cn'\nimport { DEFAULT_FOLDER_INDEX_FILE as CANONICAL_FOLDER_INDEX_FILE } from '../../utils/doc-tree-nav'\n\nexport interface NavigationNode {\n id: string\n name: string\n path: string\n type: 'file' | 'folder'\n hasReadme?: boolean\n children?: NavigationNode[]\n slug?: string\n // Mirror DocNode's extra optional fields so a DocNode tree is structurally\n // assignable to NavigationNode[] without an `as` cast. NavigationNode\n // intentionally never reads these — they're carried for type-compat only.\n documentType?: 'markdown' | 'pdf' | 'google_sheet' | 'figma' | 'file'\n sortOrder?: number\n}\n\ninterface MultiLevelNavigationProps {\n nodes: NavigationNode[]\n selectedPath: string\n expandedNodes: Set<string>\n onNodeClick: (node: NavigationNode) => void\n onToggleExpand?: (nodeId: string) => void\n isLoading?: boolean\n className?: string\n /** Folder-index filename (default `'README.md'`, case-insensitive). When the\n * selectedPath is a folder with this file, the visual ribbon moves to the\n * child file. */\n folderIndexFile?: string\n}\n\n// SSOT lives in `doc-tree-nav` (canonical case `'README.md'`). The visual\n// comparator below lowercases both sides, so re-exporting the canonical\n// constant under the same local name preserves call-site syntax without\n// drifting from the SSOT.\nconst DEFAULT_FOLDER_INDEX_FILE = CANONICAL_FOLDER_INDEX_FILE\n\n/**\n * Compute the visual \"selected\" state for a navigation node.\n *\n * Folder-with-README convention: when `selectedPath` is a folder that has a\n * README, the FOLDER doesn't get the ribbon — its README child does. The folder\n * is just a container; the README is the actual document being viewed.\n *\n * Mirror case: a README file whose parent folder is the `selectedPath` gets the\n * visual selection ribbon (implicit). This handles both the auto-select-first-\n * folder landing path AND the user-clicks-folder case.\n */\nfunction isNodeVisuallySelected(\n node: NavigationNode,\n selectedPath: string,\n folderIndexFile: string = DEFAULT_FOLDER_INDEX_FILE,\n): boolean {\n const FOLDER_INDEX_FILE = folderIndexFile.toLowerCase()\n // Path comparisons keyed on `node.path` (raw, includes `.md`). Do NOT use\n // `node.name` — the tree-builder runs it through `formatDocName` which strips\n // the `.md` extension, so `node.name.toLowerCase() === 'readme.md'` is always\n // false.\n if (selectedPath === node.path) {\n // Explicit match — but folder-with-README defers to its README child.\n return !(node.type === 'folder' && node.hasReadme)\n }\n if (node.type !== 'file') return false\n const pathLower = node.path.toLowerCase()\n // Implicit: README child whose parent folder is the selectedPath.\n if (\n selectedPath !== '' &&\n pathLower === `${selectedPath.toLowerCase()}/${FOLDER_INDEX_FILE}`\n ) {\n return true\n }\n // Implicit: ROOT README on the landing page (selectedPath === '' means\n // \"no explicit doc selected\" — the viewer falls back to the root folder-\n // index file, so that file IS the active document).\n if (selectedPath === '' && pathLower === FOLDER_INDEX_FILE) {\n return true\n }\n return false\n}\n\nexport function MultiLevelNavigation({\n nodes,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n isLoading,\n className,\n folderIndexFile = DEFAULT_FOLDER_INDEX_FILE,\n}: MultiLevelNavigationProps) {\n if (isLoading) {\n return (\n <div className={cn(\"space-y-2\", className)}>\n {[1, 2, 3, 4].map((i) => (\n <div key={i} className=\"p-3 bg-ods-skeleton rounded-lg animate-pulse h-12\" />\n ))}\n </div>\n )\n }\n\n return (\n <div className={cn(\"space-y-2\", className)} role=\"list\" aria-label=\"Navigation list\">\n {nodes.map(node => (\n <NavigationItem\n key={node.id}\n node={node}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={0}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n )\n}\n\ninterface NavigationItemProps {\n node: NavigationNode\n selectedPath: string\n expandedNodes: Set<string>\n onNodeClick: (node: NavigationNode) => void\n onToggleExpand?: (nodeId: string) => void\n level: number\n folderIndexFile: string\n}\n\nfunction NavigationItem({\n node,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n level,\n folderIndexFile,\n}: NavigationItemProps) {\n const isExpanded = expandedNodes.has(node.id)\n const isSelected = isNodeVisuallySelected(node, selectedPath, folderIndexFile)\n const hasChildren = node.children && node.children.length > 0\n\n return (\n <div className=\"space-y-1\" role=\"listitem\">\n <div className={cn(\"px-2\", level > 0 && \"ml-4\")}>\n <div\n className={cn(\n \"w-full rounded-lg transition-all duration-150 border relative\",\n isSelected\n ? \"bg-ods-border border-ods-border\"\n : \"bg-ods-card border-ods-border\"\n )}\n >\n <div className=\"flex items-center relative\">\n <button\n className={cn(\n \"w-full flex items-center h-12 px-2 rounded-lg text-[16px] font-medium font-['DM_Sans'] transition-all duration-150 leading-[1.33em] text-ods-text-primary\",\n !isSelected && \"hover:bg-ods-bg-secondary\",\n hasChildren && \"pr-12\"\n )}\n onClick={() => onNodeClick(node)}\n aria-label={`${isSelected ? 'Selected' : 'Select'} ${node.name}`}\n >\n <span className=\"flex-shrink-0 mr-2\">\n {node.type === 'folder' ? (\n isExpanded ? <FolderOpen className=\"h-4 w-4\" /> : <Folder className=\"h-4 w-4\" />\n ) : (\n <FileText className=\"h-4 w-4\" />\n )}\n </span>\n <span className=\"text-left truncate flex-1 min-w-0\">\n {node.name.endsWith('.md') ? node.name.replace('.md', '') : node.name}\n </span>\n {node.type === 'folder' && node.hasReadme && (\n <span className=\"text-[10px] bg-ods-bg-secondary text-ods-text-tertiary px-1.5 py-0.5 rounded mr-2\">\n README\n </span>\n )}\n </button>\n\n {hasChildren && (\n <button\n className=\"absolute right-0 top-0 flex items-center justify-center w-12 h-12 text-ods-text-secondary hover:text-ods-text-primary transition-colors duration-150\"\n onClick={(e) => {\n e.stopPropagation()\n if (onToggleExpand) {\n onToggleExpand(node.id)\n } else {\n onNodeClick(node)\n }\n }}\n aria-label={`${isExpanded ? 'Collapse' : 'Expand'} ${node.name}`}\n >\n {isExpanded ? <ChevronUp className=\"h-4 w-4\" /> : <ChevronDown className=\"h-4 w-4\" />}\n </button>\n )}\n </div>\n\n {isSelected && (\n <div className=\"absolute right-0 top-1/2 -translate-y-1/2 w-1 h-[calc(100%-8px)] bg-ods-accent rounded-l\" />\n )}\n </div>\n </div>\n\n {hasChildren && isExpanded && (\n <div className=\"space-y-1\">\n {node.children!.map(child => (\n <NavigationItem\n key={child.id}\n node={child}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={level + 1}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n )}\n </div>\n )\n}\n\nexport function MobileNavigationDropdown({\n nodes,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n isLoading,\n className,\n folderIndexFile = DEFAULT_FOLDER_INDEX_FILE,\n}: MultiLevelNavigationProps) {\n if (isLoading) {\n return (\n <div className={cn(\"space-y-2\", className)}>\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"p-3 bg-ods-skeleton rounded-lg animate-pulse h-10\" />\n ))}\n </div>\n )\n }\n\n return (\n <div className={cn(\"space-y-2\", className)}>\n {nodes.map(node => (\n <MobileNavigationItem\n key={node.id}\n node={node}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={0}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n )\n}\n\nfunction MobileNavigationItem({\n node,\n selectedPath,\n expandedNodes,\n onNodeClick,\n onToggleExpand,\n level,\n folderIndexFile,\n}: NavigationItemProps) {\n const isExpanded = expandedNodes.has(node.id)\n const isSelected = isNodeVisuallySelected(node, selectedPath, folderIndexFile)\n const hasChildren = node.children && node.children.length > 0\n\n return (\n <div className=\"space-y-1\">\n <div className={cn(\"px-2\", level > 0 && \"ml-3\")}>\n <div\n className={cn(\n \"w-full rounded-lg transition-all duration-150 border relative\",\n isSelected\n ? \"bg-ods-border border-ods-border\"\n : \"bg-ods-card border-ods-border\"\n )}\n >\n <div className=\"flex items-center relative\">\n <button\n className={cn(\n \"w-full flex items-center h-11 px-2 rounded-lg text-[15px] font-medium font-['DM_Sans'] transition-all duration-150 leading-[1.33em] text-ods-text-primary\",\n !isSelected && \"hover:bg-ods-bg-secondary\",\n hasChildren && \"pr-11\"\n )}\n onClick={() => onNodeClick(node)}\n >\n <span className=\"flex-shrink-0 mr-2\">\n {node.type === 'folder' ? (\n isExpanded ? <FolderOpen className=\"h-4 w-4\" /> : <Folder className=\"h-4 w-4\" />\n ) : (\n <FileText className=\"h-4 w-4\" />\n )}\n </span>\n <span className=\"text-left truncate flex-1 min-w-0 text-sm\">\n {node.name.endsWith('.md') ? node.name.replace('.md', '') : node.name}\n </span>\n {node.type === 'folder' && node.hasReadme && (\n <span className=\"text-[10px] bg-ods-bg-secondary text-ods-text-tertiary px-1.5 py-0.5 rounded mr-2\">\n README\n </span>\n )}\n </button>\n\n {hasChildren && (\n <button\n className=\"absolute right-0 top-0 flex items-center justify-center w-11 h-11 text-ods-text-secondary hover:text-ods-text-primary transition-colors duration-150\"\n onClick={(e) => {\n e.stopPropagation()\n if (onToggleExpand) {\n onToggleExpand(node.id)\n } else {\n onNodeClick(node)\n }\n }}\n aria-label={`${isExpanded ? 'Collapse' : 'Expand'} ${node.name}`}\n >\n {isExpanded ? <ChevronUp className=\"h-3.5 w-3.5\" /> : <ChevronDown className=\"h-3.5 w-3.5\" />}\n </button>\n )}\n </div>\n\n {isSelected && (\n <div className=\"absolute right-0 top-1/2 -translate-y-1/2 w-1 h-[calc(100%-8px)] bg-ods-accent rounded-l\" />\n )}\n </div>\n </div>\n\n {hasChildren && isExpanded && (\n <div className=\"space-y-1\">\n {node.children!.map(child => (\n <MobileNavigationItem\n key={child.id}\n node={child}\n selectedPath={selectedPath}\n expandedNodes={expandedNodes}\n onNodeClick={onNodeClick}\n onToggleExpand={onToggleExpand}\n level={level + 1}\n folderIndexFile={folderIndexFile}\n />\n ))}\n </div>\n )}\n </div>\n )\n}\n"],"mappings":"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","names":["cn","b","jsx","jsxs","DEFAULT_FOLDER_INDEX_FILE"]}