@flamingo-stack/openframe-frontend-core 0.0.581 → 0.0.582-1960.5386.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (105) hide show
  1. package/dist/{chunk-R5JVRFSW.js → chunk-4KWAHIYA.js} +3 -3
  2. package/dist/{chunk-KAEVE7W2.js → chunk-646JEAY4.js} +2 -2
  3. package/dist/{chunk-GAHKWML7.js → chunk-6EUYXDV3.js} +6 -6
  4. package/dist/{chunk-HVSZYUGP.js → chunk-7HHRJTIY.js} +5 -5
  5. package/dist/{chunk-JW53WPB5.js → chunk-A3SVEK7A.js} +3 -3
  6. package/dist/{chunk-6U45VX5J.cjs → chunk-A5VD6R7N.cjs} +43 -43
  7. package/dist/{chunk-6U45VX5J.cjs.map → chunk-A5VD6R7N.cjs.map} +1 -1
  8. package/dist/{chunk-BTKMTVN5.cjs → chunk-ALSLHGBH.cjs} +4 -4
  9. package/dist/{chunk-BTKMTVN5.cjs.map → chunk-ALSLHGBH.cjs.map} +1 -1
  10. package/dist/{chunk-SEZ25E22.cjs → chunk-BQPCCBGO.cjs} +10 -10
  11. package/dist/{chunk-SEZ25E22.cjs.map → chunk-BQPCCBGO.cjs.map} +1 -1
  12. package/dist/{chunk-LWDIDSCF.js → chunk-BUOYMIFJ.js} +3 -3
  13. package/dist/{chunk-3GXNCJZI.js → chunk-CR7GPMCM.js} +3 -3
  14. package/dist/{chunk-U3W2ZVN4.cjs → chunk-DGZPX2G3.cjs} +159 -159
  15. package/dist/{chunk-U3W2ZVN4.cjs.map → chunk-DGZPX2G3.cjs.map} +1 -1
  16. package/dist/{chunk-PBRXT7TD.cjs → chunk-DQVM4FTL.cjs} +10 -10
  17. package/dist/{chunk-PBRXT7TD.cjs.map → chunk-DQVM4FTL.cjs.map} +1 -1
  18. package/dist/{chunk-HJVKPG55.js → chunk-F227VMPL.js} +125 -15
  19. package/dist/{chunk-HJVKPG55.js.map → chunk-F227VMPL.js.map} +1 -1
  20. package/dist/{chunk-EZVABFEO.js → chunk-FRWQ67UM.js} +2 -2
  21. package/dist/{chunk-DSPQVZFE.cjs → chunk-FYVL6A4Z.cjs} +24 -24
  22. package/dist/{chunk-DSPQVZFE.cjs.map → chunk-FYVL6A4Z.cjs.map} +1 -1
  23. package/dist/{chunk-VXUUSLD5.js → chunk-GD3OAM4T.js} +2 -2
  24. package/dist/{chunk-ESWXXWCL.js → chunk-HQF7VE7B.js} +2 -2
  25. package/dist/{chunk-VDRCKHML.cjs → chunk-J5I3TKAH.cjs} +3 -3
  26. package/dist/{chunk-VDRCKHML.cjs.map → chunk-J5I3TKAH.cjs.map} +1 -1
  27. package/dist/{chunk-GUC4JK6O.cjs → chunk-K5SCFOD5.cjs} +9 -9
  28. package/dist/{chunk-GUC4JK6O.cjs.map → chunk-K5SCFOD5.cjs.map} +1 -1
  29. package/dist/{chunk-UKHM2S6I.cjs → chunk-KAOXOYRT.cjs} +15 -15
  30. package/dist/{chunk-UKHM2S6I.cjs.map → chunk-KAOXOYRT.cjs.map} +1 -1
  31. package/dist/{chunk-IHFQ5VVR.js → chunk-KBOBPKMU.js} +5 -5
  32. package/dist/{chunk-GMQEHDN2.cjs → chunk-L4E2TOSF.cjs} +5 -5
  33. package/dist/{chunk-GMQEHDN2.cjs.map → chunk-L4E2TOSF.cjs.map} +1 -1
  34. package/dist/{chunk-YU2UJPAT.cjs → chunk-LOJ5KQ6B.cjs} +466 -356
  35. package/dist/chunk-LOJ5KQ6B.cjs.map +1 -0
  36. package/dist/{chunk-M4KQWGEL.cjs → chunk-ORQQZQBB.cjs} +32 -32
  37. package/dist/{chunk-M4KQWGEL.cjs.map → chunk-ORQQZQBB.cjs.map} +1 -1
  38. package/dist/{chunk-ZEZMGXYV.js → chunk-QDSVTIO5.js} +5 -5
  39. package/dist/{chunk-KNBXZ22O.cjs → chunk-R3J2ERH2.cjs} +3 -3
  40. package/dist/{chunk-KNBXZ22O.cjs.map → chunk-R3J2ERH2.cjs.map} +1 -1
  41. package/dist/{chunk-WHFEG6UU.cjs → chunk-RMOUILHT.cjs} +22 -22
  42. package/dist/{chunk-WHFEG6UU.cjs.map → chunk-RMOUILHT.cjs.map} +1 -1
  43. package/dist/{chunk-OKA3DJJ3.js → chunk-SKGMU7TZ.js} +2 -2
  44. package/dist/{chunk-7CP5KY5W.js → chunk-SVQVAGZK.js} +3 -3
  45. package/dist/{chunk-W2TXSZTM.cjs → chunk-T32XVOFP.cjs} +21 -21
  46. package/dist/{chunk-W2TXSZTM.cjs.map → chunk-T32XVOFP.cjs.map} +1 -1
  47. package/dist/{chunk-GVM37TRU.js → chunk-U2ZRM3YB.js} +4 -4
  48. package/dist/{chunk-AIALG25H.cjs → chunk-WQ236GO3.cjs} +69 -69
  49. package/dist/{chunk-AIALG25H.cjs.map → chunk-WQ236GO3.cjs.map} +1 -1
  50. package/dist/components/case-studies/index.cjs +9 -9
  51. package/dist/components/case-studies/index.js +3 -3
  52. package/dist/components/chat/index.cjs +2 -2
  53. package/dist/components/chat/index.js +1 -1
  54. package/dist/components/contact/index.cjs +4 -4
  55. package/dist/components/contact/index.js +3 -3
  56. package/dist/components/docs/index.cjs +7 -7
  57. package/dist/components/docs/index.js +6 -6
  58. package/dist/components/embeds/index.cjs +4 -4
  59. package/dist/components/embeds/index.js +3 -3
  60. package/dist/components/faq/index.cjs +4 -4
  61. package/dist/components/faq/index.js +3 -3
  62. package/dist/components/features/index.cjs +5 -3
  63. package/dist/components/features/index.cjs.map +1 -1
  64. package/dist/components/features/index.js +4 -2
  65. package/dist/components/features/video.d.ts +12 -0
  66. package/dist/components/features/video.d.ts.map +1 -1
  67. package/dist/components/help-center-pages/index.cjs +23 -23
  68. package/dist/components/help-center-pages/index.js +14 -14
  69. package/dist/components/index.cjs +123 -121
  70. package/dist/components/index.cjs.map +1 -1
  71. package/dist/components/index.js +15 -13
  72. package/dist/components/index.js.map +1 -1
  73. package/dist/components/meeting-scheduler/index.cjs +21 -21
  74. package/dist/components/meeting-scheduler/index.js +2 -2
  75. package/dist/components/navigation/index.cjs +6 -6
  76. package/dist/components/navigation/index.js +5 -5
  77. package/dist/components/onboarding-guides/index.cjs +6 -6
  78. package/dist/components/onboarding-guides/index.js +5 -5
  79. package/dist/components/tickets/index.cjs +7 -7
  80. package/dist/components/tickets/index.js +6 -6
  81. package/dist/components/ui/index.cjs +3 -3
  82. package/dist/components/ui/index.js +2 -2
  83. package/dist/index.cjs +9 -7
  84. package/dist/index.cjs.map +1 -1
  85. package/dist/index.js +8 -6
  86. package/dist/index.js.map +1 -1
  87. package/package.json +1 -1
  88. package/src/components/features/__tests__/video-playback-failure.test.tsx +250 -0
  89. package/src/components/features/video.tsx +198 -16
  90. package/dist/chunk-YU2UJPAT.cjs.map +0 -1
  91. /package/dist/{chunk-R5JVRFSW.js.map → chunk-4KWAHIYA.js.map} +0 -0
  92. /package/dist/{chunk-KAEVE7W2.js.map → chunk-646JEAY4.js.map} +0 -0
  93. /package/dist/{chunk-GAHKWML7.js.map → chunk-6EUYXDV3.js.map} +0 -0
  94. /package/dist/{chunk-HVSZYUGP.js.map → chunk-7HHRJTIY.js.map} +0 -0
  95. /package/dist/{chunk-JW53WPB5.js.map → chunk-A3SVEK7A.js.map} +0 -0
  96. /package/dist/{chunk-LWDIDSCF.js.map → chunk-BUOYMIFJ.js.map} +0 -0
  97. /package/dist/{chunk-3GXNCJZI.js.map → chunk-CR7GPMCM.js.map} +0 -0
  98. /package/dist/{chunk-EZVABFEO.js.map → chunk-FRWQ67UM.js.map} +0 -0
  99. /package/dist/{chunk-VXUUSLD5.js.map → chunk-GD3OAM4T.js.map} +0 -0
  100. /package/dist/{chunk-ESWXXWCL.js.map → chunk-HQF7VE7B.js.map} +0 -0
  101. /package/dist/{chunk-IHFQ5VVR.js.map → chunk-KBOBPKMU.js.map} +0 -0
  102. /package/dist/{chunk-ZEZMGXYV.js.map → chunk-QDSVTIO5.js.map} +0 -0
  103. /package/dist/{chunk-OKA3DJJ3.js.map → chunk-SKGMU7TZ.js.map} +0 -0
  104. /package/dist/{chunk-7CP5KY5W.js.map → chunk-SVQVAGZK.js.map} +0 -0
  105. /package/dist/{chunk-GVM37TRU.js.map → chunk-U2ZRM3YB.js.map} +0 -0
package/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  SoftwareInfo,
10
10
  SoftwareSourceBadge,
11
11
  ToolBadge
12
- } from "./chunk-3GXNCJZI.js";
12
+ } from "./chunk-CR7GPMCM.js";
13
13
  import {
14
14
  AppHeader,
15
15
  AppLayout,
@@ -28,14 +28,14 @@ import {
28
28
  SlidingSidebar,
29
29
  TicketAlertsButton,
30
30
  TopNavigation
31
- } from "./chunk-GVM37TRU.js";
31
+ } from "./chunk-U2ZRM3YB.js";
32
32
  import {
33
33
  MobileNavigationDropdown,
34
34
  MultiLevelNavigation,
35
35
  StickySectionNav,
36
36
  useSectionNavigation
37
- } from "./chunk-KAEVE7W2.js";
38
- import "./chunk-R5JVRFSW.js";
37
+ } from "./chunk-646JEAY4.js";
38
+ import "./chunk-4KWAHIYA.js";
39
39
  import {
40
40
  ADMIN_APPROVAL_REQUEST_CONTEXT_TYPE,
41
41
  AIEnrichButton,
@@ -577,7 +577,7 @@ import {
577
577
  withFirstTouchAttribution,
578
578
  withWalkthroughOpenParam,
579
579
  writeDismissCookie
580
- } from "./chunk-VXUUSLD5.js";
580
+ } from "./chunk-GD3OAM4T.js";
581
581
  import "./chunk-L2SLFCXP.js";
582
582
  import "./chunk-BJXDHKUX.js";
583
583
  import {
@@ -892,6 +892,7 @@ import {
892
892
  TooltipProvider,
893
893
  TooltipTrigger,
894
894
  UnarchiveChatModal,
895
+ VIDEO_PLAYBACK_FAILED_EVENT,
895
896
  Video,
896
897
  VideoBiteCard,
897
898
  VideoBitesStrip,
@@ -1054,7 +1055,7 @@ import {
1054
1055
  useVideoOriginPreconnect,
1055
1056
  useVideoWarmup,
1056
1057
  usesCanonicalStatusStyle
1057
- } from "./chunk-HJVKPG55.js";
1058
+ } from "./chunk-F227VMPL.js";
1058
1059
  import {
1059
1060
  HEADING_EMOJI_RE,
1060
1061
  HUB_HEADER_OFFSET_PX,
@@ -2504,6 +2505,7 @@ export {
2504
2505
  UserIcon,
2505
2506
  UsersGroupIcon,
2506
2507
  UsersIcon,
2508
+ VIDEO_PLAYBACK_FAILED_EVENT,
2507
2509
  VendorDirectoryIcon,
2508
2510
  VendorShowcaseMainIcon,
2509
2511
  VendorsIcon,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/components/logs-list.tsx","../src/components/log-severity-dot.tsx","../src/types/logs.types.ts","../src/assets/index.ts"],"sourcesContent":["import { type FC, forwardRef, useRef } from 'react';\nimport type { LogsListProps, LogEntry } from '../types/logs.types';\nimport { cn } from '../utils/cn';\nimport { LogSeverityDot } from './log-severity-dot';\nimport { ToolIcon } from './tool-icon';\n\nconst formatTimestamp = (timestamp: string | Date): string => {\n const date = timestamp instanceof Date ? timestamp : new Date(timestamp);\n\n // UTC getters so the timestamp is identical on server (UTC) and client\n // (local) — otherwise React #418 hydration mismatch.\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const hours = String(date.getUTCHours()).padStart(2, '0');\n const minutes = String(date.getUTCMinutes()).padStart(2, '0');\n\n return `${year}/${month}/${day},${hours}:${minutes}`;\n};\n\nconst LogCard: FC<{\n log: LogEntry;\n isLast: boolean;\n showConnector: boolean;\n onClick?: () => void;\n}> = ({ log, isLast, showConnector, onClick }) => {\n return (\n <div className=\"relative\">\n <div\n className={cn(\n 'relative box-border flex w-full items-start gap-3 rounded px-1 py-2',\n 'cursor-pointer transition-colors hover:bg-ods-bg-hover/50',\n )}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n >\n <LogSeverityDot severity={log.severity} size=\"md\" />\n\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"text-ods-text-primary text-h6\">{log.title}</p>\n <div className=\"flex items-center gap-2\">\n <p className=\"text-ods-text-secondary text-h5\">{formatTimestamp(log.timestamp)}</p>\n {log.toolType && <ToolIcon toolType={log.toolType} size={16} />}\n </div>\n </div>\n </div>\n\n {showConnector && !isLast && (\n <div\n className=\"absolute left-[15px] w-[2px] bg-ods-border\"\n style={{\n top: '28px',\n bottom: '-10px',\n }}\n aria-hidden=\"true\"\n />\n )}\n </div>\n );\n};\n\nexport const LogsList = forwardRef<HTMLDivElement, LogsListProps>(\n (\n {\n logs,\n maxHeight = '400px',\n showConnector = true,\n onLogClick,\n loading = false,\n emptyMessage = 'No logs to display',\n className,\n },\n ref,\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const isFullHeight = maxHeight === '100%';\n\n const getContainerStyles = () => {\n if (isFullHeight) return undefined;\n return { maxHeight, minHeight: '200px' };\n };\n\n const getContainerClasses = () => {\n if (isFullHeight) return 'h-full';\n return '';\n };\n\n if (loading) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">Loading logs...</div>\n </div>\n );\n }\n\n if (logs.length === 0) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">{emptyMessage}</div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className={cn(\n 'relative rounded-lg border border-ods-border bg-ods-card',\n 'flex flex-col overflow-hidden',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div ref={containerRef} className=\"flex-1 overflow-y-auto px-4 py-3\">\n {logs.map((log, index) => (\n <LogCard\n key={log.id}\n log={log}\n isLast={index === logs.length - 1}\n showConnector={showConnector}\n onClick={() => onLogClick?.(log)}\n />\n ))}\n </div>\n </div>\n );\n },\n);\n\nLogsList.displayName = 'LogsList';\n","import { forwardRef } from 'react';\nimport { type LogSeverityDotProps, LOG_SEVERITY_COLORS, LOG_SEVERITY_LABELS } from '../types/logs.types';\nimport { cn } from '../utils/cn';\n\nexport const LogSeverityDot = forwardRef<HTMLDivElement, LogSeverityDotProps>(\n ({ severity, size = 'md', className }, ref) => {\n const sizeClasses = {\n sm: 'w-4 h-4',\n md: 'w-6 h-6',\n lg: 'w-8 h-8',\n };\n\n const dotSizeClasses = {\n sm: 'w-2 h-2',\n md: 'w-3 h-3',\n lg: 'w-4 h-4',\n };\n\n return (\n <div\n ref={ref}\n className={cn('relative flex shrink-0 items-center justify-center', sizeClasses[size], className)}\n aria-label={`${LOG_SEVERITY_LABELS[severity]} severity`}\n >\n <div\n className={cn('rounded-full', dotSizeClasses[size])}\n style={{ backgroundColor: LOG_SEVERITY_COLORS[severity] }}\n />\n </div>\n );\n },\n);\n\nLogSeverityDot.displayName = 'LogSeverityDot';\n","import type { ToolType } from './tool.types';\n\nexport type LogSeverity = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL';\n\nexport interface LogEntry {\n id: string;\n ingestDay: string;\n eventType: string;\n severity: LogSeverity;\n title: string;\n timestamp: string | Date;\n toolType?: ToolType;\n toolIcon?: string;\n message?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface LogsListProps {\n logs: LogEntry[];\n maxHeight?: string | number;\n showConnector?: boolean;\n onLogClick?: (log: LogEntry) => void;\n loading?: boolean;\n emptyMessage?: string;\n className?: string;\n}\n\nexport interface LogSeverityDotProps {\n severity: LogSeverity;\n size?: 'sm' | 'md' | 'lg';\n className?: string;\n}\n\nexport const LOG_SEVERITY_COLORS: Record<LogSeverity, string> = {\n DEBUG: '#888888',\n INFO: '#888888',\n WARNING: '#e1b32f',\n ERROR: '#f36666',\n CRITICAL: '#b43b3b',\n};\n\nexport const LOG_SEVERITY_LABELS: Record<LogSeverity, string> = {\n DEBUG: 'Debug',\n INFO: 'Info',\n WARNING: 'Warning',\n ERROR: 'Error',\n CRITICAL: 'Critical',\n};\n","// Assets exports\n// Font files and other static assets\n// Platform utilities will be available when needed\n\nexport const assets = {} as const;\nexport * from './fae-avatar';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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","names":["forwardRef","jsx","forwardRef"]}
1
+ {"version":3,"sources":["../src/components/logs-list.tsx","../src/components/log-severity-dot.tsx","../src/types/logs.types.ts","../src/assets/index.ts"],"sourcesContent":["import { type FC, forwardRef, useRef } from 'react';\nimport type { LogsListProps, LogEntry } from '../types/logs.types';\nimport { cn } from '../utils/cn';\nimport { LogSeverityDot } from './log-severity-dot';\nimport { ToolIcon } from './tool-icon';\n\nconst formatTimestamp = (timestamp: string | Date): string => {\n const date = timestamp instanceof Date ? timestamp : new Date(timestamp);\n\n // UTC getters so the timestamp is identical on server (UTC) and client\n // (local) — otherwise React #418 hydration mismatch.\n const year = date.getUTCFullYear();\n const month = String(date.getUTCMonth() + 1).padStart(2, '0');\n const day = String(date.getUTCDate()).padStart(2, '0');\n const hours = String(date.getUTCHours()).padStart(2, '0');\n const minutes = String(date.getUTCMinutes()).padStart(2, '0');\n\n return `${year}/${month}/${day},${hours}:${minutes}`;\n};\n\nconst LogCard: FC<{\n log: LogEntry;\n isLast: boolean;\n showConnector: boolean;\n onClick?: () => void;\n}> = ({ log, isLast, showConnector, onClick }) => {\n return (\n <div className=\"relative\">\n <div\n className={cn(\n 'relative box-border flex w-full items-start gap-3 rounded px-1 py-2',\n 'cursor-pointer transition-colors hover:bg-ods-bg-hover/50',\n )}\n onClick={onClick}\n role=\"button\"\n tabIndex={0}\n onKeyDown={e => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n onClick?.();\n }\n }}\n >\n <LogSeverityDot severity={log.severity} size=\"md\" />\n\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"text-ods-text-primary text-h6\">{log.title}</p>\n <div className=\"flex items-center gap-2\">\n <p className=\"text-ods-text-secondary text-h5\">{formatTimestamp(log.timestamp)}</p>\n {log.toolType && <ToolIcon toolType={log.toolType} size={16} />}\n </div>\n </div>\n </div>\n\n {showConnector && !isLast && (\n <div\n className=\"absolute left-[15px] w-[2px] bg-ods-border\"\n style={{\n top: '28px',\n bottom: '-10px',\n }}\n aria-hidden=\"true\"\n />\n )}\n </div>\n );\n};\n\nexport const LogsList = forwardRef<HTMLDivElement, LogsListProps>(\n (\n {\n logs,\n maxHeight = '400px',\n showConnector = true,\n onLogClick,\n loading = false,\n emptyMessage = 'No logs to display',\n className,\n },\n ref,\n ) => {\n const containerRef = useRef<HTMLDivElement>(null);\n\n const isFullHeight = maxHeight === '100%';\n\n const getContainerStyles = () => {\n if (isFullHeight) return undefined;\n return { maxHeight, minHeight: '200px' };\n };\n\n const getContainerClasses = () => {\n if (isFullHeight) return 'h-full';\n return '';\n };\n\n if (loading) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">Loading logs...</div>\n </div>\n );\n }\n\n if (logs.length === 0) {\n return (\n <div\n ref={ref}\n className={cn(\n 'rounded-lg border border-ods-border bg-ods-card p-4',\n 'flex items-center justify-center',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div className=\"text-ods-text-muted text-h6\">{emptyMessage}</div>\n </div>\n );\n }\n\n return (\n <div\n ref={ref}\n className={cn(\n 'relative rounded-lg border border-ods-border bg-ods-card',\n 'flex flex-col overflow-hidden',\n getContainerClasses(),\n className,\n )}\n style={getContainerStyles()}\n >\n <div ref={containerRef} className=\"flex-1 overflow-y-auto px-4 py-3\">\n {logs.map((log, index) => (\n <LogCard\n key={log.id}\n log={log}\n isLast={index === logs.length - 1}\n showConnector={showConnector}\n onClick={() => onLogClick?.(log)}\n />\n ))}\n </div>\n </div>\n );\n },\n);\n\nLogsList.displayName = 'LogsList';\n","import { forwardRef } from 'react';\nimport { type LogSeverityDotProps, LOG_SEVERITY_COLORS, LOG_SEVERITY_LABELS } from '../types/logs.types';\nimport { cn } from '../utils/cn';\n\nexport const LogSeverityDot = forwardRef<HTMLDivElement, LogSeverityDotProps>(\n ({ severity, size = 'md', className }, ref) => {\n const sizeClasses = {\n sm: 'w-4 h-4',\n md: 'w-6 h-6',\n lg: 'w-8 h-8',\n };\n\n const dotSizeClasses = {\n sm: 'w-2 h-2',\n md: 'w-3 h-3',\n lg: 'w-4 h-4',\n };\n\n return (\n <div\n ref={ref}\n className={cn('relative flex shrink-0 items-center justify-center', sizeClasses[size], className)}\n aria-label={`${LOG_SEVERITY_LABELS[severity]} severity`}\n >\n <div\n className={cn('rounded-full', dotSizeClasses[size])}\n style={{ backgroundColor: LOG_SEVERITY_COLORS[severity] }}\n />\n </div>\n );\n },\n);\n\nLogSeverityDot.displayName = 'LogSeverityDot';\n","import type { ToolType } from './tool.types';\n\nexport type LogSeverity = 'DEBUG' | 'INFO' | 'WARNING' | 'ERROR' | 'CRITICAL';\n\nexport interface LogEntry {\n id: string;\n ingestDay: string;\n eventType: string;\n severity: LogSeverity;\n title: string;\n timestamp: string | Date;\n toolType?: ToolType;\n toolIcon?: string;\n message?: string;\n metadata?: Record<string, unknown>;\n}\n\nexport interface LogsListProps {\n logs: LogEntry[];\n maxHeight?: string | number;\n showConnector?: boolean;\n onLogClick?: (log: LogEntry) => void;\n loading?: boolean;\n emptyMessage?: string;\n className?: string;\n}\n\nexport interface LogSeverityDotProps {\n severity: LogSeverity;\n size?: 'sm' | 'md' | 'lg';\n className?: string;\n}\n\nexport const LOG_SEVERITY_COLORS: Record<LogSeverity, string> = {\n DEBUG: '#888888',\n INFO: '#888888',\n WARNING: '#e1b32f',\n ERROR: '#f36666',\n CRITICAL: '#b43b3b',\n};\n\nexport const LOG_SEVERITY_LABELS: Record<LogSeverity, string> = {\n DEBUG: 'Debug',\n INFO: 'Info',\n WARNING: 'Warning',\n ERROR: 'Error',\n CRITICAL: 'Critical',\n};\n","// Assets exports\n// Font files and other static assets\n// Platform utilities will be available when needed\n\nexport const assets = {} as const;\nexport * from './fae-avatar';\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;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","names":["forwardRef","jsx","forwardRef"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flamingo-stack/openframe-frontend-core",
3
- "version": "0.0.581",
3
+ "version": "0.0.582-1960.5386.1",
4
4
  "description": "Shared design system and components for all Flamingo platforms",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -0,0 +1,250 @@
1
+ /**
2
+ * Playback-failure handling for the shared `<Video>`.
3
+ *
4
+ * Two branches: the file player (MuxPlayer) via a fake element that fires the
5
+ * media events the watchdog listens to, and the YouTube facade via the
6
+ * postMessage channel it subscribes to. Both assert the same contract — a
7
+ * failure surfaces a reachable retry AND is reported once.
8
+ */
9
+ import { render, screen, act, fireEvent } from '@testing-library/react';
10
+ import { forwardRef, useImperativeHandle } from 'react';
11
+ import { beforeEach, afterEach, describe, expect, it, vi } from 'vitest';
12
+
13
+ interface FakeMuxEl {
14
+ paused: boolean;
15
+ ended: boolean;
16
+ currentTime: number;
17
+ loadCalls: number;
18
+ playCalls: number;
19
+ listeners: Record<string, Array<() => void>>;
20
+ emit: (type: string) => void;
21
+ }
22
+
23
+ let muxProps: Record<string, (() => void) | undefined> = {};
24
+ let muxEl: FakeMuxEl | null = null;
25
+
26
+ vi.mock('@mux/mux-player-react', () => {
27
+ const Fake = forwardRef((props: Record<string, unknown>, ref: React.Ref<unknown>) => {
28
+ muxProps = props as Record<string, (() => void) | undefined>;
29
+ useImperativeHandle(ref, () => {
30
+ const listeners: Record<string, Array<() => void>> = {};
31
+ const el = {
32
+ paused: false,
33
+ ended: false,
34
+ loadCalls: 0,
35
+ playCalls: 0,
36
+ muted: false,
37
+ volume: 1,
38
+ currentTime: 0,
39
+ duration: 100,
40
+ listeners,
41
+ play: () => {
42
+ el.playCalls += 1;
43
+ el.paused = false;
44
+ return Promise.resolve();
45
+ },
46
+ pause: () => {
47
+ el.paused = true;
48
+ },
49
+ // Mirrors the real element: a reload rewinds to zero.
50
+ load: () => {
51
+ el.loadCalls += 1;
52
+ el.currentTime = 0;
53
+ },
54
+ addEventListener: (type: string, fn: () => void) => {
55
+ (listeners[type] ??= []).push(fn);
56
+ },
57
+ removeEventListener: (type: string, fn: () => void) => {
58
+ listeners[type] = (listeners[type] ?? []).filter(l => l !== fn);
59
+ },
60
+ emit: (type: string) => {
61
+ for (const fn of [...(listeners[type] ?? [])]) fn();
62
+ },
63
+ };
64
+ muxEl = el;
65
+ return el;
66
+ }, []);
67
+ return <div data-testid="mux" />;
68
+ });
69
+ Fake.displayName = 'FakeMuxPlayer';
70
+ return { default: Fake };
71
+ });
72
+
73
+ import { Video, VIDEO_PLAYBACK_FAILED_EVENT } from '../video';
74
+
75
+ const YT_ORIGIN = 'https://www.youtube-nocookie.com';
76
+ const ERROR_TEXT = 'This video failed to play.';
77
+
78
+ function ytMessage(body: unknown): void {
79
+ window.dispatchEvent(new MessageEvent('message', { data: JSON.stringify(body), origin: YT_ORIGIN }));
80
+ }
81
+
82
+ function failureEvents(spy: { mock: { calls: unknown[][] } }): CustomEvent[] {
83
+ return spy.mock.calls
84
+ .map(call => call[0])
85
+ .filter((e): e is CustomEvent => e instanceof CustomEvent && e.type === VIDEO_PLAYBACK_FAILED_EVENT);
86
+ }
87
+
88
+ describe('Video — file playback failure handling', () => {
89
+ beforeEach(() => {
90
+ vi.useFakeTimers();
91
+ muxProps = {};
92
+ muxEl = null;
93
+ });
94
+ afterEach(() => {
95
+ vi.runOnlyPendingTimers();
96
+ vi.useRealTimers();
97
+ // Re-spying on a live spy returns it with its calls intact — restore, or
98
+ // the next test counts this one's events.
99
+ vi.restoreAllMocks();
100
+ });
101
+
102
+ it('a mid-playback stall surfaces a retry and reports the failure once', () => {
103
+ const dispatched = vi.spyOn(window, 'dispatchEvent');
104
+ render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
105
+
106
+ // Buffering while playback is expected — the watchdog arms.
107
+ act(() => muxProps.onWaiting?.());
108
+ expect(screen.queryByText(ERROR_TEXT)).toBeNull();
109
+
110
+ // No progress within the window → failure surfaces.
111
+ act(() => {
112
+ vi.advanceTimersByTime(15_000);
113
+ });
114
+ expect(screen.getByText(ERROR_TEXT)).toBeInTheDocument();
115
+ expect(screen.getByRole('button', { name: 'Try again' })).toBeInTheDocument();
116
+
117
+ const events = failureEvents(dispatched);
118
+ expect(events).toHaveLength(1);
119
+ expect(events[0].detail).toMatchObject({ kind: 'file', reason: 'stall' });
120
+ });
121
+
122
+ it('Try again reloads and resumes the element, clearing the error', () => {
123
+ render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
124
+ act(() => muxProps.onWaiting?.());
125
+ act(() => {
126
+ vi.advanceTimersByTime(15_000);
127
+ });
128
+
129
+ fireEvent.click(screen.getByRole('button', { name: 'Try again' }));
130
+ expect(muxEl?.loadCalls).toBe(1);
131
+ expect(muxEl?.playCalls).toBe(1);
132
+ expect(screen.queryByText(ERROR_TEXT)).toBeNull();
133
+ });
134
+
135
+ it('Try again resumes from where the stall happened, not from the top', () => {
136
+ render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
137
+ if (muxEl) muxEl.currentTime = 480;
138
+ act(() => muxProps.onWaiting?.());
139
+ act(() => {
140
+ vi.advanceTimersByTime(15_000);
141
+ });
142
+
143
+ fireEvent.click(screen.getByRole('button', { name: 'Try again' }));
144
+ expect(muxEl?.currentTime).toBe(0); // reload rewound it
145
+ act(() => muxEl?.emit('loadedmetadata'));
146
+ expect(muxEl?.currentTime).toBe(480);
147
+ // One-shot: a later reload must not drag the old position back.
148
+ expect(muxEl?.listeners.loadedmetadata).toHaveLength(0);
149
+ });
150
+
151
+ it('a hard media error surfaces the retry immediately', () => {
152
+ render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
153
+ act(() => muxProps.onError?.());
154
+ expect(screen.getByText(ERROR_TEXT)).toBeInTheDocument();
155
+ });
156
+
157
+ it('a stall that self-heals dismisses its own overlay', () => {
158
+ render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
159
+ act(() => muxProps.onWaiting?.());
160
+ act(() => {
161
+ vi.advanceTimersByTime(15_000);
162
+ });
163
+ expect(screen.getByText(ERROR_TEXT)).toBeInTheDocument();
164
+
165
+ act(() => muxProps.onPlaying?.());
166
+ expect(screen.queryByText(ERROR_TEXT)).toBeNull();
167
+ });
168
+
169
+ it('a paused player is never treated as a stall', () => {
170
+ render(<Video kind="file" url="https://example.com/a.m3u8" layout="centered" />);
171
+ if (muxEl) muxEl.paused = true;
172
+ act(() => muxProps.onWaiting?.());
173
+ act(() => {
174
+ vi.advanceTimersByTime(15_000);
175
+ });
176
+ expect(screen.queryByText(ERROR_TEXT)).toBeNull();
177
+ });
178
+
179
+ it('decorative first-frame previews stay silent on error', () => {
180
+ render(<Video kind="file" url="https://example.com/a.m3u8" firstFrameOnly />);
181
+ act(() => muxProps.onError?.());
182
+ expect(screen.queryByText(ERROR_TEXT)).toBeNull();
183
+ });
184
+ });
185
+
186
+ describe('Video — YouTube facade failure handling', () => {
187
+ beforeEach(() => {
188
+ vi.useFakeTimers();
189
+ });
190
+ afterEach(() => {
191
+ vi.runOnlyPendingTimers();
192
+ vi.useRealTimers();
193
+ vi.restoreAllMocks();
194
+ });
195
+
196
+ function activate(): void {
197
+ render(<Video kind="youtube" url="dQw4w9WgXcQ" />);
198
+ fireEvent.click(screen.getByRole('button', { name: /Play:/ }));
199
+ }
200
+
201
+ it('an explicit embed error surfaces retry + a YouTube fallback link', () => {
202
+ const dispatched = vi.spyOn(window, 'dispatchEvent');
203
+ activate();
204
+ act(() => ytMessage({ event: 'onError', info: 150 }));
205
+
206
+ expect(screen.getByText(ERROR_TEXT)).toBeInTheDocument();
207
+ expect(screen.getByRole('link', { name: 'Watch on YouTube' })).toHaveAttribute(
208
+ 'href',
209
+ 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
210
+ );
211
+ expect(failureEvents(dispatched)[0].detail).toMatchObject({
212
+ kind: 'youtube',
213
+ reason: 'error',
214
+ errorCode: 150,
215
+ });
216
+ });
217
+
218
+ // Regression guard: a silent embed is NOT a failed one. Safari/iOS block the
219
+ // unmuted autoplay and wait for a click inside the iframe, and a broken jsapi
220
+ // channel reports nothing at all — a readiness timeout would tear both down.
221
+ it('a silent embed is left alone, however long it stays silent', () => {
222
+ const dispatched = vi.spyOn(window, 'dispatchEvent');
223
+ activate();
224
+ act(() => {
225
+ vi.advanceTimersByTime(120_000);
226
+ });
227
+
228
+ expect(screen.queryByText(ERROR_TEXT)).toBeNull();
229
+ expect(screen.getByTitle('YouTube Video')).toBeInTheDocument();
230
+ expect(failureEvents(dispatched)).toHaveLength(0);
231
+ });
232
+
233
+ it('reaching PLAYING keeps the embed and never shows the error', () => {
234
+ activate();
235
+ act(() => ytMessage({ event: 'infoDelivery', info: { playerState: 1 } }));
236
+ act(() => {
237
+ vi.advanceTimersByTime(12_000);
238
+ });
239
+ expect(screen.queryByText(ERROR_TEXT)).toBeNull();
240
+ });
241
+
242
+ it('Try again remounts the embed', () => {
243
+ activate();
244
+ act(() => ytMessage({ event: 'onError', info: 150 }));
245
+ fireEvent.click(screen.getByRole('button', { name: 'Try again' }));
246
+ expect(screen.queryByText(ERROR_TEXT)).toBeNull();
247
+ expect(screen.queryByRole('link', { name: 'Watch on YouTube' })).toBeNull();
248
+ expect(screen.getByTitle('YouTube Video')).toBeInTheDocument();
249
+ });
250
+ });