@linktr.ee/messaging-react 3.37.0 → 3.37.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.
- package/README.md +4 -4
- package/dist/Card-415OfRhw.cjs.map +1 -1
- package/dist/{Card-D4PPD5qa.js → Card-DVMzYquF.js} +11 -8
- package/dist/Card-DVMzYquF.js.map +1 -0
- package/dist/{Card-qS17qnN0.js → Card-DaJtDL7m.js} +15 -6
- package/dist/Card-DaJtDL7m.js.map +1 -0
- package/dist/Card-Dxk_Ggxw.cjs.map +1 -1
- package/dist/LoadingDots-Ct3WmVlT.js.map +1 -1
- package/dist/LoadingDots-D9GGphgM.cjs.map +1 -1
- package/dist/{index-DGmVyxHy.js → index-BOBIiAuD.js} +87 -62
- package/dist/index-BOBIiAuD.js.map +1 -0
- package/dist/index-D7A8U8GQ.cjs.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -2
- package/src/components/ActionButton/ActionButton.stories.tsx +0 -2
- package/src/components/ActionButton/ActionButton.test.tsx +60 -55
- package/src/components/ActionButton/index.tsx +13 -14
- package/src/components/AttachmentCard/AttachmentCard.stories.tsx +1 -3
- package/src/components/AttachmentCard/MediaPlayer.tsx +9 -7
- package/src/components/AttachmentCard/index.tsx +6 -2
- package/src/components/Avatar/Avatar.stories.tsx +17 -5
- package/src/components/Avatar/index.tsx +13 -23
- package/src/components/ChannelView.test.tsx +6 -10
- package/src/components/CustomMessage/CustomMessage.stories.tsx +7 -2
- package/src/components/CustomMessage/CustomMessage.test.tsx +5 -3
- package/src/components/CustomMessage/LockedAttachment/LockedAttachment.test.tsx +5 -4
- package/src/components/CustomMessage/LockedAttachment/components/Text/Card.tsx +2 -4
- package/src/components/CustomMessage/LockedAttachment/components/_shared/CardBody.tsx +9 -6
- package/src/components/CustomMessage/LockedAttachment/components/_shared/CardThumbnail.tsx +1 -3
- package/src/components/CustomMessage/LockedAttachment/components/_shared/GalleryThumbnail.tsx +1 -4
- package/src/components/CustomMessage/LockedAttachment/components/_shared/PurchaseStatusBadge.tsx +0 -1
- package/src/components/CustomMessage/LockedAttachment/components/_shared/SingleThumbnail.tsx +1 -4
- package/src/components/CustomMessage/LockedAttachment/components/_shared/SkeletonTextLines.tsx +7 -2
- package/src/components/CustomMessage/LockedAttachment/types.ts +4 -3
- package/src/components/CustomMessage/MessageAttachmentConversations.stories.tsx +89 -86
- package/src/components/CustomMessage/MessageVoteButtons.tsx +4 -1
- package/src/components/CustomMessage/SentMessageDeliveryStatus.test.tsx +13 -10
- package/src/components/CustomMessage/SentMessageDeliveryStatus.tsx +1 -4
- package/src/components/CustomMessage/StreamAttachmentMessage.stories.tsx +4 -4
- package/src/components/CustomMessage/StreamAttachmentMessage.test.tsx +6 -7
- package/src/components/CustomMessage/context.tsx +7 -2
- package/src/components/CustomMessage/index.tsx +1 -4
- package/src/components/CustomMessageInput/CustomMessageInput.stories.tsx +33 -11
- package/src/components/CustomMessageInput/CustomMessageInput.test.tsx +3 -3
- package/src/components/CustomSystemMessage/CustomSystemMessage.test.tsx +6 -9
- package/src/components/CustomTypingIndicator/CustomTypingIndicator.stories.tsx +4 -1
- package/src/components/IconButton/index.tsx +21 -17
- package/src/components/LinkAttachment/LinkAttachment.test.tsx +4 -1
- package/src/components/LinkAttachment/components/_shared/CardCta.tsx +4 -1
- package/src/components/Loading/Loading.stories.tsx +1 -4
- package/src/components/MediaMessage/MediaMessage.stories.tsx +6 -18
- package/src/components/MediaMessage/MediaMessage.test.tsx +30 -16
- package/src/components/MediaMessage/index.tsx +51 -14
- package/src/components/MessageAttachment/Audio/AudioAttachment.stories.tsx +4 -2
- package/src/components/MessageAttachment/File/FileAttachment.stories.tsx +3 -1
- package/src/components/MessageAttachment/Image/ImageAttachment.stories.tsx +15 -4
- package/src/components/MessageAttachment/Image/index.tsx +6 -12
- package/src/components/MessageAttachment/MessageAttachment.test.tsx +59 -27
- package/src/components/MessageAttachment/Pdf/PdfAttachment.stories.tsx +3 -1
- package/src/components/MessageAttachment/Pdf/index.tsx +2 -3
- package/src/components/MessageAttachment/Video/VideoAttachment.stories.tsx +11 -3
- package/src/components/MessageAttachment/Video/index.tsx +9 -14
- package/src/components/MessageAttachment/_shared/CompactDocumentRow.tsx +1 -3
- package/src/components/MessageAttachment/_shared/ImageViewer.tsx +1 -3
- package/src/components/MessageAttachment/_shared/MediaStackGrid.tsx +5 -1
- package/src/components/MessageAttachment/_shared/PdfViewer.tsx +1 -3
- package/src/components/MessageAttachment/_shared/VideoViewer.tsx +1 -3
- package/src/components/MessageAttachment/_shared/fileMeta.test.ts +4 -6
- package/src/components/MessageAttachment/stories/StoryTable.tsx +1 -3
- package/src/components/MessageBubble/MessageBubble.stories.tsx +5 -1
- package/src/components/MessageBubble/MessageBubble.test.tsx +1 -3
- package/src/components/MessageBubble/components/_shared/bubbleTail.tsx +2 -1
- package/src/components/MessagingShell/LoadingState.stories.tsx +0 -1
- package/src/components/RichCard/RichCard.stories.tsx +14 -3
- package/src/components/RichCard/RichCard.test.tsx +16 -4
- package/src/components/RichCard/RichCardBody.tsx +11 -3
- package/src/components/RichCard/RichCardCard.tsx +1 -3
- package/src/components/RichCard/RichCardImages.tsx +2 -4
- package/src/components/SearchInput/SearchInput.stories.tsx +0 -1
- package/src/components/SearchInput/SearchInput.test.tsx +94 -65
- package/src/hooks/useChannelStar.ts +3 -1
- package/src/hooks/useMessaging.ts +4 -4
- package/src/stories/decorators/storyUser.tsx +2 -2
- package/src/stories/mocks.tsx +4 -1
- package/src/styles.css +23 -8
- package/src/types.ts +1 -2
- package/src/utils/cdnImageUrl.test.ts +3 -1
- package/dist/Card-D4PPD5qa.js.map +0 -1
- package/dist/Card-qS17qnN0.js.map +0 -1
- package/dist/index-DGmVyxHy.js.map +0 -1
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index-DGmVyxHy.js","sources":["../src/providers/MessagingProvider.tsx","../src/hooks/useMessaging.ts","../src/hooks/useChannelStar.ts","../src/utils/resolveConversationParticipant.ts","../src/utils/resolveParticipantDisplayName.ts","../src/components/Avatar/getAvatarEmoji.ts","../src/components/Avatar/index.tsx","../src/hooks/useChannelModerationActions.ts","../src/components/ActionButton/index.tsx","../src/components/ChannelActionsMenu/index.tsx","../src/components/ChannelHeaderRedesign.tsx","../src/components/CustomDateSeparator/index.tsx","../src/hooks/useMessageVote.ts","../src/utils/getMessageDisplayText.ts","../src/utils/cdnImageUrl.ts","../src/components/AttachmentCard/utils/mimeType.ts","../src/components/AttachmentCard/utils/icons.ts","../src/components/AttachmentCard/MediaPlayer.tsx","../src/components/AttachmentCard/Thumbnail.tsx","../src/components/AttachmentCard/index.tsx","../src/components/MediaMessage/index.tsx","../src/components/MessageAttachment/_shared/Bubble.tsx","../src/components/MessageAttachment/_shared/DismissButton.tsx","../src/components/MessageAttachment/types.ts","../src/components/MessageAttachment/Audio/index.tsx","../src/components/MessageAttachment/_shared/fileMeta.ts","../src/components/MessageAttachment/_shared/CompactDocumentRow.tsx","../src/components/MessageAttachment/_shared/triggerDownload.ts","../src/components/MessageAttachment/File/index.tsx","../src/components/MessageAttachment/_shared/CarouselNav.tsx","../src/components/MessageAttachment/_shared/DownloadAction.tsx","../src/components/MessageAttachment/_shared/useCarousel.ts","../src/components/MessageAttachment/_shared/ViewerShell.tsx","../src/components/MessageAttachment/_shared/ImageViewer.tsx","../src/components/MessageAttachment/_shared/MediaStackGrid.tsx","../src/components/MessageAttachment/_shared/useViewer.ts","../src/components/MessageAttachment/Image/index.tsx","../src/components/MessageAttachment/_shared/PdfViewer.tsx","../src/components/MessageAttachment/Pdf/index.tsx","../src/components/MessageAttachment/_shared/VideoViewer.tsx","../src/components/MessageAttachment/Video/index.tsx","../src/components/MessageAttachment/index.tsx","../src/components/CustomMessage/BroadcastNameContext.ts","../src/components/CustomMessage/LockedAttachment/types.ts","../src/components/CustomMessage/context.tsx","../src/components/CustomMessage/LockedAttachment/index.tsx","../src/components/CustomMessage/MessageTag.tsx","../src/components/CustomMessage/MessageVoteButtons.tsx","../src/components/CustomMessage/SentMessageDeliveryStatus.tsx","../src/components/LinkAttachment/components/_shared/normalizeExternalHref.ts","../src/components/LinkAttachment/components/_shared/CardCta.tsx","../src/components/LinkAttachment/components/_shared/CardBody.tsx","../src/components/LinkAttachment/components/_shared/CardShell.tsx","../src/components/LinkAttachment/components/_shared/CardThumbnail.tsx","../src/components/LinkAttachment/components/_shared/hex.ts","../src/components/LinkAttachment/components/_shared/useChinPalette.ts","../src/components/LinkAttachment/components/Composer/Card.tsx","../src/components/LinkAttachment/components/_shared/BubbleTail.tsx","../src/components/LinkAttachment/components/Received/Card.tsx","../src/components/LinkAttachment/components/Sent/Card.tsx","../src/components/LinkAttachment/index.tsx","../src/components/CustomMessage/StreamAttachmentMessage.tsx","../src/components/CustomMessage/index.tsx","../src/components/CustomMessage/CustomMessageActions.tsx","../src/components/CustomLinkPreviewList/index.tsx","../src/components/CustomMessageInput/index.tsx","../src/components/CustomSystemMessage/index.tsx","../src/components/CustomTypingIndicator/DmAgentContext.ts","../src/components/CustomTypingIndicator/index.tsx","../src/components/MessagingShell/ChannelEmptyState.tsx","../src/components/Loading/index.tsx","../src/components/MessagingShell/LoadingState.tsx","../src/components/ChannelView.tsx","../src/components/MessagingShell/ErrorState.tsx","../src/components/MessagingShell/index.tsx","../src/hooks/useRestorePendingMessages.ts","../src/components/ChannelList/ChannelListContext.tsx","../src/utils/formatRelativeTime.ts","../src/components/ChannelList/CustomChannelPreview.tsx","../src/components/ChannelList/index.tsx","../src/components/RichCard/RichCardBody.tsx","../src/components/RichCard/RichCardImages.tsx","../src/components/RichCard/RichCardCard.tsx","../src/components/RichCard/index.tsx","../src/components/MessageBubble/components/_shared/bubbleTail.tsx","../src/components/MessageBubble/components/_shared/BubbleShell.tsx","../src/components/MessageBubble/components/Received/Card.tsx","../src/components/MessageBubble/components/Sent/Card.tsx","../src/components/MessageBubble/index.tsx","../src/components/FaqList/FaqListItem.tsx","../src/components/FaqList/index.tsx"],"sourcesContent":["/* eslint-disable react-hooks/refs, react-hooks/set-state-in-effect -- pre-existing pattern flagged by the React\n Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is\n out of scope for the lint toolchain upgrade. */\nimport { StreamChatService } from '@linktr.ee/messaging-core'\nimport React, {\n createContext,\n useContext,\n useEffect,\n useState,\n useRef,\n useCallback,\n} from 'react'\nimport { StreamChat } from 'stream-chat'\nimport { Chat } from 'stream-chat-react'\n\nimport type { MessagingProviderProps, MessagingCapabilities } from '../types'\n\n/**\n * Context value for messaging state and service\n */\nexport interface MessagingContextValue {\n service: StreamChatService | null\n client: StreamChat | null // Stream Chat client\n isConnected: boolean\n isLoading: boolean\n error: string | null\n capabilities: MessagingCapabilities\n refreshConnection: () => Promise<void>\n debug: boolean\n}\n\nconst MessagingContext = createContext<MessagingContextValue>({\n service: null,\n client: null,\n isConnected: false,\n isLoading: false,\n error: null,\n capabilities: {},\n refreshConnection: async () => {},\n debug: false,\n})\n\n/**\n * Hook to access messaging context\n */\nexport const useMessagingContext = () => useContext(MessagingContext)\n\n/**\n * Provider component that wraps messaging-core with React state management\n */\nexport const MessagingProvider: React.FC<MessagingProviderProps> = ({\n children,\n user,\n serviceConfig,\n apiKey,\n capabilities = {},\n debug = false,\n client: injectedClient,\n}) => {\n // Create debug logger that respects the debug prop\n const debugLog = useCallback(\n (message: string, ...args: unknown[]) => {\n if (debug) {\n console.log(`🔥 [MessagingProvider] ${message}`, ...args)\n }\n },\n [debug]\n )\n\n debugLog('🔄 RENDER START', {\n userId: user?.id,\n apiKey: apiKey?.substring(0, 8) + '...',\n serviceConfig: !!serviceConfig,\n capabilities: Object.keys(capabilities),\n })\n\n const [service, setService] = useState<StreamChatService | null>(null)\n const [client, setClient] = useState<StreamChat | null>(null)\n const [isConnected, setIsConnected] = useState(false)\n const [isLoading, setIsLoading] = useState(false)\n const [error, setError] = useState<string | null>(null)\n\n // Prevent multiple concurrent connection attempts\n const connectingRef = useRef(false)\n\n // The single service instance, for teardown in the client-cleanup effect.\n const serviceRef = useRef<StreamChatService | null>(null)\n\n // serviceConfig / debug are read at service-creation time via refs so that\n // changing them does not recreate the service (and re-adopt the live client).\n // The service is created once per client lifetime (apiKey); the connection is\n // driven by `user`. Real consumers pass a stable, memoized serviceConfig.\n const serviceConfigRef = useRef(serviceConfig)\n serviceConfigRef.current = serviceConfig\n const debugRef = useRef(debug)\n debugRef.current = debug\n\n // Track renders and prop changes\n const prevPropsRef = useRef({\n userId: user?.id,\n apiKey,\n serviceConfig,\n capabilities,\n })\n const renderCountRef = useRef(0)\n renderCountRef.current++\n\n debugLog('📊 RENDER INFO', {\n renderCount: renderCountRef.current,\n currentProps: { userId: user?.id, apiKey: apiKey?.substring(0, 8) + '...' },\n propChanges: {\n userChanged: prevPropsRef.current.userId !== user?.id,\n apiKeyChanged: prevPropsRef.current.apiKey !== apiKey,\n serviceConfigChanged:\n prevPropsRef.current.serviceConfig !== serviceConfig,\n capabilitiesChanged: prevPropsRef.current.capabilities !== capabilities,\n },\n })\n\n prevPropsRef.current = {\n userId: user?.id,\n apiKey,\n serviceConfig,\n capabilities,\n }\n\n // Construct the Stream client synchronously so <Chat> can render against a\n // stable instance on the very first render. Without this the subtree mounted\n // as a bare `children` while connecting, then remounted inside <Chat> once\n // connectUser resolved (~0.8s later) because the children's parent element\n // changed — re-running every mount effect (refetches, Stream listeners,\n // page-view events) and resetting transient state. This client is injected\n // into the service below, so connectUser connects this exact instance.\n //\n // Stored in a ref (not useMemo) so its identity is *guaranteed* stable for the\n // apiKey's lifetime. useMemo is unsafe here: React may discard a memo and\n // recompute it, which would mint a new client, re-run the service-init effect,\n // and tear down + rebuild the live connection — the very remount this fix\n // exists to prevent. Both effects below key on `chatClient`, so it must not\n // depend on memo retention. One client per provider mount (keyed on apiKey) —\n // not the process-wide StreamChat.getInstance, which is shared across keys and\n // provider instances.\n const clientRef = useRef<{ apiKey: string; client: StreamChat } | null>(null)\n if (!injectedClient && apiKey && clientRef.current?.apiKey !== apiKey) {\n clientRef.current = { apiKey, client: new StreamChat(apiKey) }\n }\n // An injected client takes precedence and short-circuits the apiKey-derived\n // client, the service, and the connect/disconnect lifecycle below — the\n // caller owns that instance (offline/dev via `createMockMessagingClient`, or\n // a test). Otherwise fall back to the apiKey-derived client as before.\n const chatClient = injectedClient\n ? injectedClient\n : apiKey\n ? (clientRef.current?.client ?? null)\n : null\n\n // Create the service once per client lifetime (apiKey). Keyed on `chatClient`\n // — NOT on serviceConfig/debug — so those changing does not spin up a second\n // service that would re-adopt the live client and race the in-flight connect.\n // serviceConfig/debug are read from refs at creation. There is therefore a\n // single service per client, which owns the connection lifecycle and tracks\n // the active user; `user` changes reconnect through it.\n useEffect(() => {\n const currentRender = renderCountRef.current\n const currentConfig = serviceConfigRef.current\n\n // An injected client owns its own connection — never create a service or\n // connect for it. Reset all apiKey-path connection state (service, client,\n // isConnected, error, isLoading): while injected these are masked by the\n // context, but on a later return to apiKey mode they would leak back —\n // before the fresh service reconnects — feeding the user-connection effect\n // a stale service and MessagingShell (which gates its load on\n // `client && isConnected`) a disconnected client to queryChannels on.\n if (injectedClient) {\n serviceRef.current = null\n setService(null)\n setClient(null)\n setIsConnected(false)\n setError(null)\n setIsLoading(false)\n return\n }\n\n if (!apiKey || !chatClient || !currentConfig) {\n debugLog('⚠️ SERVICE INIT SKIPPED', {\n renderCount: currentRender,\n reason: 'Missing apiKey, client, or serviceConfig',\n })\n return\n }\n\n debugLog('🚀 CREATING SERVICE', {\n renderCount: currentRender,\n apiKey: apiKey?.substring(0, 8) + '...',\n })\n\n const newService = new StreamChatService({\n ...currentConfig,\n apiKey,\n debug: debugRef.current,\n client: chatClient,\n })\n\n serviceRef.current = newService\n setService(newService)\n debugLog('✅ SERVICE SET', {\n renderCount: currentRender,\n serviceInstance: !!newService,\n })\n\n // No disconnect on cleanup here — disconnection is owned by the\n // client-cleanup effect below, keyed on `chatClient` (apiKey) and unmount.\n // eslint-disable-next-line react-hooks/exhaustive-deps -- serviceConfig/debug/debugLog are read via refs by design (see above); recreating on them would re-adopt the live client\n }, [apiKey, chatClient, injectedClient])\n\n // Track if we've already connected this user with this service to prevent duplicate connections\n const connectedUserRef = useRef<{\n serviceId: StreamChatService\n userId: string\n } | null>(null)\n\n // Connect user when service and user are available\n useEffect(() => {\n debugLog('🔗 USER CONNECTION EFFECT TRIGGERED', {\n hasService: !!service,\n hasUser: !!user,\n userId: user?.id,\n isConnecting: connectingRef.current,\n isConnected: isConnected,\n dependencies: { service: !!service, userId: user?.id },\n })\n\n // The injected client owns its own connection — never connect a service for\n // it. Without this guard a `user` change in injected-client mode would run\n // service.connectUser on a stale apiKey-path service (the toggle case).\n if (injectedClient) {\n return\n }\n\n if (!service || !user) {\n debugLog('⚠️ USER CONNECTION SKIPPED', 'Missing service or user')\n return\n }\n\n if (connectingRef.current) {\n debugLog('⚠️ USER CONNECTION SKIPPED', 'Already connecting')\n return\n }\n\n // Check if we've already connected this exact user with this exact service instance\n if (\n connectedUserRef.current?.serviceId === service &&\n connectedUserRef.current?.userId === user.id\n ) {\n debugLog(\n '⚠️ USER CONNECTION SKIPPED',\n 'Already connected this user with this service'\n )\n return\n }\n\n const connectUser = async () => {\n debugLog('🚀 STARTING USER CONNECTION', { userId: user.id })\n connectingRef.current = true\n setIsLoading(true)\n setError(null)\n\n try {\n debugLog('📞 CALLING SERVICE.CONNECTUSER', { userId: user.id })\n const streamClient = await service.connectUser(user)\n setClient(streamClient)\n setIsConnected(true)\n connectedUserRef.current = { serviceId: service, userId: user.id } // Mark as connected\n debugLog('✅ USER CONNECTION SUCCESS', {\n userId: user.id,\n clientId: streamClient.userID,\n })\n } catch (err) {\n const errorMessage =\n err instanceof Error ? err.message : 'Connection failed'\n setError(errorMessage)\n debugLog('❌ USER CONNECTION ERROR', {\n userId: user.id,\n error: errorMessage,\n })\n } finally {\n setIsLoading(false)\n connectingRef.current = false\n debugLog('🔄 USER CONNECTION FINISHED', {\n userId: user.id,\n isConnected,\n })\n }\n }\n\n connectUser()\n }, [service, user, debugLog, isConnected, injectedClient])\n\n // Disconnect tied to the client's lifetime: only when `chatClient` changes\n // (i.e. `apiKey` changes) or the provider unmounts — never on a mere\n // serviceConfig/debug change, which reuses the same client. Uses `serviceRef`\n // so the disconnect goes through the latest service instance. Deliberately\n // keyed on `chatClient` only: depending on `debugLog` (which changes identity\n // when the `debug` prop toggles) would tear down the live client just for a\n // logging change.\n useEffect(() => {\n const client = chatClient\n return () => {\n if (!client) return\n connectedUserRef.current = null // Reset connection tracking\n serviceRef.current?.disconnectUser().catch(console.error)\n }\n }, [chatClient])\n\n const refreshConnection = useCallback(async () => {\n debugLog('🔄 REFRESH CONNECTION CALLED', {\n hasService: !!service,\n hasUser: !!user,\n })\n\n // The injected client owns its own connection lifecycle — refresh is a\n // no-op. Guarding here (not just the context fields) stops a stale\n // apiKey-path `service` from being disconnected/reconnected if the same\n // provider previously connected via apiKey and then received a client.\n if (injectedClient) {\n return\n }\n\n if (!service || !user) {\n debugLog('⚠️ REFRESH CONNECTION SKIPPED', 'Missing service or user')\n return\n }\n\n debugLog('🚀 STARTING CONNECTION REFRESH', { userId: user.id })\n setIsLoading(true)\n try {\n debugLog('🔌 DISCONNECTING FOR REFRESH')\n await service.disconnectUser()\n debugLog('📞 RECONNECTING FOR REFRESH')\n const streamClient = await service.connectUser(user)\n setClient(streamClient)\n setIsConnected(true)\n setError(null)\n debugLog('✅ CONNECTION REFRESH SUCCESS', { userId: user.id })\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'Refresh failed'\n setError(errorMessage)\n debugLog('❌ CONNECTION REFRESH ERROR', {\n userId: user.id,\n error: errorMessage,\n })\n } finally {\n setIsLoading(false)\n debugLog('🔄 CONNECTION REFRESH FINISHED', { userId: user.id })\n }\n }, [service, user, debugLog, injectedClient])\n\n // Memoize context value to prevent unnecessary re-renders\n const contextValue: MessagingContextValue = React.useMemo(() => {\n debugLog('💫 CONTEXT VALUE MEMOIZATION', {\n hasService: !!service,\n hasClient: !!client,\n isConnected,\n isLoading,\n hasError: !!error,\n capabilitiesKeys: Object.keys(capabilities),\n })\n\n return {\n // With an injected client, expose no service — the injected client owns\n // its lifecycle and there is no backend to reach. Suppressing it stops\n // service-dependent paths (channel creation, block/unblock) from calling\n // a stale apiKey-path service if the same provider previously connected.\n service: injectedClient ? null : service,\n // An injected client is already connected and owns its lifecycle, so\n // surface it directly and report a clean connected state — the apiKey\n // path's `client`/`isLoading`/`error` (from connectUser) must not leak\n // through if the same provider previously attempted an apiKey connect.\n client: injectedClient ?? client,\n isConnected: injectedClient ? true : isConnected,\n isLoading: injectedClient ? false : isLoading,\n error: injectedClient ? null : error,\n capabilities,\n refreshConnection,\n debug,\n }\n }, [\n service,\n client,\n injectedClient,\n isConnected,\n isLoading,\n error,\n capabilities,\n refreshConnection,\n debug,\n debugLog,\n ])\n\n debugLog('🔄 RENDER END', {\n renderCount: renderCountRef.current,\n willRenderChat: !!chatClient,\n contextValueReady: !!contextValue,\n })\n\n return (\n <MessagingContext.Provider value={contextValue}>\n {chatClient ? (\n <Chat\n client={chatClient}\n customClasses={{\n channelList:\n 'str-chat__channel-list str-chat__channel-list-react bg-transparent lg:border-r-2 border-r-0 border-[#0000000A]',\n }}\n >\n {children}\n </Chat>\n ) : (\n children\n )}\n </MessagingContext.Provider>\n )\n}\n","import { useMessagingContext } from '../providers/MessagingProvider';\nimport type { MessagingContextValue } from '../providers/MessagingProvider';\n\n/**\n * Hook to access messaging service and state\n */\nexport const useMessaging = (): MessagingContextValue => {\n return useMessagingContext();\n};\n","/* eslint-disable react-hooks/set-state-in-effect -- pre-existing pattern flagged by the React\n Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is\n out of scope for the lint toolchain upgrade. */\nimport { useEffect, useState } from 'react'\nimport { Channel, Event } from 'stream-chat'\n\nexport const useChannelStar = (channel?: Channel) => {\n const [isChannelStarred, setIsChannelStarred] = useState(\n !!channel?.state?.membership?.pinned_at\n )\n\n useEffect(() => {\n if (!channel) {\n setIsChannelStarred(false)\n return\n }\n\n setIsChannelStarred(!!channel.state.membership?.pinned_at)\n\n const handleMemberUpdate = (event: Event) => {\n setIsChannelStarred(\n event?.member ? !!event.member.pinned_at : !!channel.state.membership?.pinned_at\n )\n }\n\n channel.on('member.updated', handleMemberUpdate)\n\n return () => {\n channel.off('member.updated', handleMemberUpdate)\n }\n }, [channel])\n\n return isChannelStarred\n}\n","import {\n isOfficialChannel,\n OFFICIAL_SENDER_USER_ID,\n} from '@linktr.ee/messaging-taxonomy'\nimport type { Channel } from 'stream-chat'\n\ntype ChannelMembers = NonNullable<Channel['state']>['members']\ntype ChannelMember = ChannelMembers[string]\n\n/**\n * Resolves the member to display as the conversation's \"other party\".\n *\n * For a normal 1:1 channel this is the member that isn't the current user. The\n * official channel is special: its display identity is always the official\n * sender (\"Linktree\"), regardless of who is viewing. That matters because an\n * unverified sensitive linker reads the official channel as a guest whose\n * random Stream id isn't a member, so the \"not me\" heuristic would otherwise\n * fall back to the linker's own member and show their name instead of Linktree.\n */\nexport function resolveConversationParticipant(\n members: ChannelMembers | undefined,\n currentUserId: string | undefined,\n channelType: string | undefined\n): ChannelMember | undefined {\n const memberList = Object.values(members || {})\n\n if (isOfficialChannel(channelType)) {\n const officialSender = memberList.find(\n (member) => member.user?.id === OFFICIAL_SENDER_USER_ID\n )\n if (officialSender) return officialSender\n }\n\n return memberList.find(\n (member) => member.user?.id && member.user.id !== currentUserId\n )\n}\n","const UUID_REGEX =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i\n\nexport type ParticipantDisplayUser = {\n id?: string\n name?: string | null\n username?: string | null\n}\n\nexport function isUuidLike(value: string): boolean {\n return UUID_REGEX.test(value.trim())\n}\n\nfunction isUsableDisplayValue(\n value: string | null | undefined,\n userId?: string\n): value is string {\n const trimmed = value?.trim()\n if (!trimmed) return false\n if (userId && trimmed === userId) return false\n // Followers with no real name were historically provisioned in Stream Chat with a\n // placeholder name of the form `Follower <id>` (the string \"Follower \" + the user id).\n // Treat it as missing so surfaces fall back to their own label instead of leaking the\n // internal placeholder as a display name (MES-1077).\n if (userId && trimmed === `Follower ${userId}`) return false\n return !isUuidLike(trimmed)\n}\n\n/**\n * Resolves a human-readable participant label from Stream user fields.\n * Never falls back to user.id. UUID-like names are treated as missing.\n */\nexport function resolveParticipantDisplayName(\n user?: ParticipantDisplayUser | null\n): string {\n if (isUsableDisplayValue(user?.name, user?.id)) {\n return user.name.trim()\n }\n\n if (isUsableDisplayValue(user?.username, user?.id)) {\n return user.username.trim()\n }\n\n return 'Unknown member'\n}\n","/**\n * Fruit emojis paired with their avatar background color.\n * Single source of truth for both `getAvatarEmoji` and `getAvatarBgColor`.\n */\nconst FRUITS = [\n { emoji: '🍎', bgColor: '#FFD1DD' }, // Apple\n { emoji: '🍌', bgColor: '#FFF1C9' }, // Banana\n { emoji: '🍇', bgColor: '#E6D9FF' }, // Grape\n { emoji: '🍊', bgColor: '#D6F4FF' }, // Orange\n { emoji: '🍓', bgColor: '#FFE0AE' }, // Strawberry\n { emoji: '🥥', bgColor: '#F7E7D8' }, // Coconut\n { emoji: '🍒', bgColor: '#DFF4D8' }, // Cherry\n { emoji: '🥭', bgColor: '#FFD8C2' }, // Mango\n { emoji: '🍉', bgColor: '#D6EBFF' }, // Watermelon\n { emoji: '🍋', bgColor: '#CAEEB5' }, // Lemon\n { emoji: '🥝', bgColor: '#FFE7C7' }, // Kiwi\n { emoji: '🫒', bgColor: '#F6E3B4' }, // Olive\n { emoji: '🍈', bgColor: '#D9F7E1' }, // Melon\n]\n\n/**\n * Simple hash function to convert string to number\n */\nfunction hashString(str: string): number {\n let hash = 0\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i)\n hash = (hash << 5) - hash + char\n hash = hash & hash // Convert to 32-bit integer\n }\n return Math.abs(hash)\n}\n\n/**\n * Pick a consistent fruit for an id string\n */\nfunction getFruit(id: string) {\n return FRUITS[hashString(id) % FRUITS.length]\n}\n\n/**\n * Get a fruit emoji based on an id string\n * @param id - The string id to generate emoji from\n * @returns A fruit emoji string\n */\nexport function getAvatarEmoji(id: string): string {\n return getFruit(id).emoji\n}\n\n/**\n * Get the background color paired with an id's fruit emoji\n * @param id - The string id to generate the color from\n * @returns A hex color string\n */\nexport function getAvatarBgColor(id: string): string {\n return getFruit(id).bgColor\n}\n","import { StarIcon } from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React from 'react'\n\nimport { getAvatarBgColor, getAvatarEmoji } from './getAvatarEmoji'\n\nexport interface AvatarProps {\n id: string\n name: string\n image?: string\n size?: number\n className?: string\n starred?: boolean\n shape?: 'squircle' | 'circle'\n dmAgentEnabled?: boolean\n}\n\n/**\n * Avatar component that displays a user image or colored initial fallback\n */\nexport const Avatar = ({\n id,\n image,\n size = 40,\n className,\n starred = false,\n shape = 'squircle',\n dmAgentEnabled = false,\n}: AvatarProps) => {\n const emoji = getAvatarEmoji(id)\n const bgColor = getAvatarBgColor(id)\n\n // Small avatars scale the emoji with the avatar so it doesn't overpower a\n // tiny circle — a fixed floor read ~60% of a 17px avatar vs ~35% at 40px.\n // Larger avatars keep fixed sizes. 0.45 is continuous with the 14px step at\n // 32px; lower it to shrink small emoji further.\n const SMALL_EMOJI_RATIO = 0.45\n const getFontSize = () => {\n if (size < 32) return Math.round(size * SMALL_EMOJI_RATIO)\n if (size < 56) return 14\n if (size < 120) return 18\n return 36\n }\n\n const fontSize = getFontSize()\n // Match design treatment: 2px AI border at 40px avatar size.\n const aiBorderWidth = size >= 40 ? 2 : 1\n\n const borderStyle =\n shape === 'circle'\n ? { borderRadius: '50%' }\n : { borderRadius: '1rem' }\n\n const avatarInner = (\n <div className=\"h-full w-full overflow-hidden\" style={borderStyle}>\n {image ? (\n <img\n src={image}\n alt=\"\"\n className=\"h-full w-full object-cover\"\n />\n ) : (\n <div\n aria-hidden=\"true\"\n className={classNames(\n 'avatar-fallback flex h-full w-full items-center justify-center font-semibold select-none transition-colors'\n )}\n style={{ fontSize, lineHeight: 1 }}\n >\n {emoji}\n </div>\n )}\n </div>\n )\n\n return (\n <div\n className={classNames(\n 'relative flex-shrink-0',\n className\n )}\n style={{\n '--str-chat__avatar-size': `${size}px`,\n width: `${size}px`,\n height: `${size}px`,\n backgroundColor: image ? undefined : bgColor,\n ...borderStyle,\n } as React.CSSProperties}\n >\n {starred && (\n <div\n aria-hidden=\"true\"\n className=\"absolute -left-1.5 -top-1.5 z-10 flex size-5 items-center justify-center rounded-full bg-white shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]\"\n >\n <StarIcon className=\"size-3 text-yellow-600\" weight=\"duotone\" />\n </div>\n )}\n <div\n data-testid=\"avatar-ring\"\n className={classNames(\n 'h-full w-full',\n 'bg-transparent'\n )}\n style={{\n ...borderStyle,\n ...(dmAgentEnabled && {\n borderWidth: `${aiBorderWidth}px`,\n borderStyle: 'solid',\n borderColor: 'var(--AI-Gradient, #7F22FE)',\n boxShadow: 'inset 0 1px 2px 0 rgba(255, 255, 255, 0.5)',\n }),\n }}\n >\n {avatarInner}\n </div>\n </div>\n )\n}\n","/* eslint-disable react-hooks/set-state-in-effect -- pre-existing pattern flagged by the React\n Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is\n out of scope for the lint toolchain upgrade. */\nimport { useEffect, useState } from 'react'\nimport type { Channel as ChannelType, ChannelMemberResponse } from 'stream-chat'\n\nimport { useMessagingContext } from '../providers/MessagingProvider'\n\n// Blocked user from Stream Chat API\ntype BlockedUser = {\n blocked_user_id: string\n}\n\nconst REPORT_URL = 'https://linktr.ee/s/about/trust-center/report'\n\nexport interface UseChannelModerationActionsParams {\n channel: ChannelType\n participant: ChannelMemberResponse | undefined\n /**\n * When false, the blocked-status lookup is skipped (the result would be\n * unused, e.g. the Linktree official channel does not offer blocking).\n * Defaults to true.\n */\n showBlockParticipant?: boolean\n /**\n * When false, the blocked-status lookup is deferred. Useful for surfaces\n * that mount the actions ahead of time (e.g. a closed popover) and only\n * need block state once visible. Defaults to true.\n */\n enabled?: boolean\n onLeaveConversation?: (channel: ChannelType) => void\n onBlockParticipant?: (participantId?: string) => void\n onDeleteConversationClick?: () => void\n onBlockParticipantClick?: () => void\n onReportParticipantClick?: () => void\n /**\n * Called after an action completes successfully (e.g. to close the\n * surrounding dialog or popover).\n */\n onActionComplete?: () => void\n /** Prefix used for debug logging. */\n logLabel?: string\n}\n\nexport interface ChannelModerationActions {\n isParticipantBlocked: boolean\n /**\n * True while the initial blocked-status lookup is in flight. Until this is\n * false, `isParticipantBlocked` has not yet been resolved and the\n * block/unblock action should not be acted on.\n */\n isCheckingBlockedStatus: boolean\n isLeaving: boolean\n isUpdatingBlockStatus: boolean\n handleLeaveConversation: () => Promise<void>\n handleBlockUser: () => Promise<void>\n handleUnblockUser: () => Promise<void>\n handleReportUser: () => void\n}\n\n/**\n * Encapsulates the conversation moderation actions (leave/delete, block,\n * unblock, report) shared by the channel info dialog and the channel actions\n * popover menu. Keeping the logic in one place ensures both surfaces stay in\n * sync.\n */\nexport const useChannelModerationActions = ({\n channel,\n participant,\n showBlockParticipant = true,\n enabled = true,\n onLeaveConversation,\n onBlockParticipant,\n onDeleteConversationClick,\n onBlockParticipantClick,\n onReportParticipantClick,\n onActionComplete,\n logLabel = 'useChannelModerationActions',\n}: UseChannelModerationActionsParams): ChannelModerationActions => {\n const { service, debug } = useMessagingContext()\n const participantId = participant?.user?.id\n const willLookup = Boolean(\n enabled && showBlockParticipant && service && participantId\n )\n\n const [isParticipantBlocked, setIsParticipantBlocked] = useState(false)\n // Tracks the participant + service the most recent lookup has completed\n // for. Keying on both so a service swap (e.g. MessagingProvider rebuilding\n // its StreamChatService when config/apiKey/debug changes) re-triggers the\n // loading state for the same participant — otherwise the menu would\n // briefly show actionable Block/Unblock against the new service with the\n // old service's blocked result. Computing the flag at render time — rather\n // than from a useState updated inside useEffect — also closes the brief\n // window where the menu would render the regular (enabled) Block button\n // before the effect flipped the disabled placeholder on.\n const [resolvedFor, setResolvedFor] = useState<{\n participantId: string\n service: unknown\n } | null>(null)\n const [isLeaving, setIsLeaving] = useState(false)\n const [isUpdatingBlockStatus, setIsUpdatingBlockStatus] = useState(false)\n\n const isCheckingBlockedStatus =\n willLookup &&\n (resolvedFor?.participantId !== participantId ||\n resolvedFor?.service !== service)\n\n // Resolve whether the participant is blocked whenever the participant or\n // surface visibility changes.\n useEffect(() => {\n // When the lookup is skipped (Block action hidden, surface disabled, or no\n // participant), clear any stale blocked state so a previous participant's\n // value can't leak into the next conversation.\n if (!willLookup || !service || !participantId) {\n setIsParticipantBlocked(false)\n setResolvedFor(null)\n return\n }\n\n let cancelled = false\n\n void (async () => {\n try {\n const blockedUsers = await service.getBlockedUsers()\n if (cancelled) return\n setIsParticipantBlocked(\n blockedUsers.some(\n (user: BlockedUser) => user.blocked_user_id === participantId\n )\n )\n } catch (error) {\n if (!cancelled) {\n console.error(`[${logLabel}] Failed to check blocked status:`, error)\n }\n } finally {\n // Mark the lookup as resolved regardless of success/failure so a\n // rejected `getBlockedUsers()` doesn't leave the menu stuck in the\n // disabled-spinner state. On failure the blocked flag stays at its\n // default (false), matching the prior behavior — the user can attempt\n // to block/unblock and the server rejects if the state is wrong.\n if (!cancelled) setResolvedFor({ participantId, service })\n }\n })()\n\n // Ignore an in-flight result if the participant/surface changes first.\n return () => {\n cancelled = true\n }\n }, [willLookup, service, participantId, logLabel])\n\n const handleLeaveConversation = async () => {\n if (isLeaving) return\n\n // Fire analytics callback before action\n onDeleteConversationClick?.()\n\n if (debug) {\n console.log(`[${logLabel}] Leave conversation`, channel.cid)\n }\n setIsLeaving(true)\n\n try {\n const actingUserId = channel._client?.userID ?? null\n await channel.hide(actingUserId, false)\n\n if (onLeaveConversation) {\n await onLeaveConversation(channel)\n }\n\n onActionComplete?.()\n } catch (error) {\n console.error(`[${logLabel}] Failed to leave conversation`, error)\n } finally {\n setIsLeaving(false)\n }\n }\n\n const handleBlockUser = async () => {\n if (isUpdatingBlockStatus || !service) return\n\n // Fire analytics callback before action\n onBlockParticipantClick?.()\n\n if (debug) {\n console.log(`[${logLabel}] Block member`, participant?.user?.id)\n }\n setIsUpdatingBlockStatus(true)\n\n try {\n await service.blockUser(participant?.user?.id)\n\n if (onBlockParticipant) {\n await onBlockParticipant(participant?.user?.id)\n }\n\n onActionComplete?.()\n } catch (error) {\n console.error(`[${logLabel}] Failed to block member`, error)\n } finally {\n setIsUpdatingBlockStatus(false)\n }\n }\n\n const handleUnblockUser = async () => {\n if (isUpdatingBlockStatus || !service) return\n\n // Fire analytics callback before action\n onBlockParticipantClick?.()\n\n if (debug) {\n console.log(`[${logLabel}] Unblock member`, participant?.user?.id)\n }\n setIsUpdatingBlockStatus(true)\n\n try {\n await service.unBlockUser(participant?.user?.id)\n\n if (onBlockParticipant) {\n await onBlockParticipant(participant?.user?.id)\n }\n\n onActionComplete?.()\n } catch (error) {\n console.error(`[${logLabel}] Failed to unblock member`, error)\n } finally {\n setIsUpdatingBlockStatus(false)\n }\n }\n\n const handleReportUser = () => {\n // Fire analytics callback before action\n onReportParticipantClick?.()\n\n onActionComplete?.()\n window.open(REPORT_URL, '_blank', 'noopener,noreferrer')\n }\n\n return {\n isParticipantBlocked,\n isCheckingBlockedStatus,\n isLeaving,\n isUpdatingBlockStatus,\n handleLeaveConversation,\n handleBlockUser,\n handleUnblockUser,\n handleReportUser,\n }\n}\n","import classNames from \"classnames\";\nimport React from \"react\";\n\nexport interface ActionButtonProps\n extends React.ButtonHTMLAttributes<HTMLButtonElement> {\n variant?: \"default\" | \"danger\";\n}\n\nconst ActionButton = ({\n variant = \"default\",\n className,\n children,\n ...rest\n}: ActionButtonProps) => {\n const isDanger = variant === \"danger\";\n return (\n <button\n type=\"button\"\n className={classNames(\n \"flex w-full items-center gap-3 rounded-lg px-4 py-3 text-left text-sm transition-colors focus-ring disabled:cursor-not-allowed disabled:opacity-60\",\n isDanger\n ? \"text-danger hover:bg-danger/50\"\n : \"text-charcoal hover:bg-sand\",\n className,\n )}\n {...rest}\n >\n {children}\n </button>\n );\n};\n\nexport default ActionButton;\n","import {\n DotsThreeIcon,\n FlagIcon,\n ProhibitInsetIcon,\n SignOutIcon,\n SpinnerGapIcon,\n} from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React, { useCallback, useEffect, useId, useRef, useState } from 'react'\nimport type { Channel as ChannelType, ChannelMemberResponse } from 'stream-chat'\n\nimport { useChannelModerationActions } from '../../hooks/useChannelModerationActions'\nimport ActionButton from '../ActionButton'\n\nexport interface ChannelActionsMenuProps {\n channel: ChannelType\n participant: ChannelMemberResponse | undefined\n showDeleteConversation?: boolean\n /**\n * Show the Block/Unblock action. Defaults to true.\n * Set false to hide it (e.g. the Linktree official channel).\n */\n showBlockParticipant?: boolean\n /**\n * Show the Report action. Defaults to true.\n * Set false to hide it (e.g. the Linktree official channel).\n */\n showReportParticipant?: boolean\n onLeaveConversation?: (channel: ChannelType) => void\n onBlockParticipant?: (participantId?: string) => void\n onDeleteConversationClick?: () => void\n onBlockParticipantClick?: () => void\n onReportParticipantClick?: () => void\n customChannelActions?: React.ReactNode\n /** Classes applied to the trigger (\"...\") button so it matches the header. */\n triggerClassName?: string\n}\n\n/**\n * Conversation overflow (\"...\") menu rendered as an anchored popover.\n *\n * Replaces the slide-in sidebar dialog for moderation actions: the trigger\n * opens a small popover that lists the available options (delete, block,\n * report) inline with their icons.\n */\nexport const ChannelActionsMenu: React.FC<ChannelActionsMenuProps> = ({\n channel,\n participant,\n showDeleteConversation = true,\n showBlockParticipant = true,\n showReportParticipant = true,\n onLeaveConversation,\n onBlockParticipant,\n onDeleteConversationClick,\n onBlockParticipantClick,\n onReportParticipantClick,\n customChannelActions,\n triggerClassName,\n}) => {\n const [isOpen, setIsOpen] = useState(false)\n const containerRef = useRef<HTMLDivElement>(null)\n const menuId = useId()\n\n const close = useCallback(() => setIsOpen(false), [])\n\n const {\n isParticipantBlocked,\n isCheckingBlockedStatus,\n isLeaving,\n isUpdatingBlockStatus,\n handleLeaveConversation,\n handleBlockUser,\n handleUnblockUser,\n handleReportUser,\n } = useChannelModerationActions({\n channel,\n participant,\n showBlockParticipant,\n enabled: isOpen,\n onLeaveConversation,\n onBlockParticipant,\n onDeleteConversationClick,\n onBlockParticipantClick,\n onReportParticipantClick,\n onActionComplete: close,\n logLabel: 'ChannelActionsMenu',\n })\n\n // Close the popover on outside click or Escape.\n useEffect(() => {\n if (!isOpen) return\n\n const handlePointerDown = (event: MouseEvent) => {\n if (\n containerRef.current &&\n !containerRef.current.contains(event.target as Node)\n ) {\n setIsOpen(false)\n }\n }\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Escape') {\n setIsOpen(false)\n }\n }\n\n document.addEventListener('mousedown', handlePointerDown)\n document.addEventListener('keydown', handleKeyDown)\n\n return () => {\n document.removeEventListener('mousedown', handlePointerDown)\n document.removeEventListener('keydown', handleKeyDown)\n }\n }, [isOpen])\n\n if (!participant) return null\n\n const hasActions =\n showDeleteConversation ||\n showBlockParticipant ||\n showReportParticipant ||\n Boolean(customChannelActions)\n\n if (!hasActions) return null\n\n return (\n <div ref={containerRef} className=\"relative\">\n <button\n className={triggerClassName}\n type=\"button\"\n aria-haspopup=\"true\"\n aria-expanded={isOpen}\n aria-controls={isOpen ? menuId : undefined}\n onClick={() => setIsOpen((open) => !open)}\n >\n <DotsThreeIcon className=\"size-5 text-black/90\" />\n <span className=\"sr-only\">More options</span>\n </button>\n\n {isOpen && (\n <div\n id={menuId}\n aria-label=\"Conversation options\"\n className={classNames(\n 'absolute right-0 top-full z-50 mt-2 w-56 overflow-hidden',\n 'rounded-lg border border-sand bg-white p-1 shadow-max-elevation-light'\n )}\n >\n <ul className=\"flex flex-col gap-1\">\n {showDeleteConversation && (\n <li>\n <ActionButton\n onClick={handleLeaveConversation}\n disabled={isLeaving}\n aria-busy={isLeaving}\n >\n {isLeaving ? (\n <SpinnerGapIcon className=\"h-5 w-5 animate-spin\" />\n ) : (\n <SignOutIcon className=\"h-5 w-5\" />\n )}\n <span>Delete Conversation</span>\n </ActionButton>\n </li>\n )}\n {showBlockParticipant && (\n <li>\n {isCheckingBlockedStatus ? (\n // Block status not yet resolved — show a disabled placeholder\n // so a premature click can't act on a stale block state.\n <ActionButton disabled aria-busy>\n <SpinnerGapIcon className=\"h-5 w-5 animate-spin\" />\n <span>Block</span>\n </ActionButton>\n ) : isParticipantBlocked ? (\n <ActionButton\n onClick={handleUnblockUser}\n disabled={isUpdatingBlockStatus}\n aria-busy={isUpdatingBlockStatus}\n >\n {isUpdatingBlockStatus ? (\n <SpinnerGapIcon className=\"h-5 w-5 animate-spin\" />\n ) : (\n <ProhibitInsetIcon className=\"h-5 w-5\" />\n )}\n <span>Unblock</span>\n </ActionButton>\n ) : (\n <ActionButton\n onClick={handleBlockUser}\n disabled={isUpdatingBlockStatus}\n aria-busy={isUpdatingBlockStatus}\n >\n {isUpdatingBlockStatus ? (\n <SpinnerGapIcon className=\"h-5 w-5 animate-spin\" />\n ) : (\n <ProhibitInsetIcon className=\"h-5 w-5\" />\n )}\n <span>Block</span>\n </ActionButton>\n )}\n </li>\n )}\n {showReportParticipant && (\n <li>\n <ActionButton variant=\"danger\" onClick={handleReportUser}>\n <FlagIcon className=\"h-5 w-5\" />\n <span>Report</span>\n </ActionButton>\n </li>\n )}\n {customChannelActions}\n </ul>\n </div>\n )}\n </div>\n )\n}\n\nexport default ChannelActionsMenu\n","import {\n ArrowLeftIcon,\n CaretRightIcon,\n CurrencyDollarIcon,\n StarIcon,\n} from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React from 'react'\nimport type { Channel as ChannelType } from 'stream-chat'\nimport { useChannelStateContext, useChatContext } from 'stream-chat-react'\n\nimport { useChannelStar } from '../hooks/useChannelStar'\nimport { resolveConversationParticipant } from '../utils/resolveConversationParticipant'\nimport { resolveParticipantDisplayName } from '../utils/resolveParticipantDisplayName'\n\nimport { Avatar } from './Avatar'\nimport { ChannelActionsMenu } from './ChannelActionsMenu'\n\nconst ICON_BTN_CLASS =\n 'flex size-10 flex-shrink-0 items-center justify-center rounded-full bg-white/65 shadow-[0px_4px_8px_0px_rgba(0,0,0,0.06),0px_0px_0px_1px_rgba(0,0,0,0.04)] backdrop-blur-[40px] transition-filter duration-150 hover:brightness-95 aria-expanded:brightness-95 focus-ring'\n\nconst CUSTOMER_PAID_TAG = 'CUSTOMER_PAID'\n\nconst isCustomerPaidTag = (value: unknown) =>\n typeof value === 'string' &&\n value\n .trim()\n .replace(/[\\s-]+/g, '_')\n .toUpperCase() === CUSTOMER_PAID_TAG\n\ninterface ChannelHeaderRedesignProps {\n className?: string\n onBack?: () => void\n showBackButton: boolean\n showStarButton?: boolean\n onLeaveConversation?: (channel: ChannelType) => void\n onBlockParticipant?: (participantId?: string) => void\n showDeleteConversation?: boolean\n showBlockParticipant?: boolean\n showReportParticipant?: boolean\n onDeleteConversationClick?: () => void\n onBlockParticipantClick?: () => void\n onReportParticipantClick?: () => void\n customChannelActions?: React.ReactNode\n renderChannelActions?: React.ReactNode\n showChannelInfo?: boolean\n onParticipantNameClick?: () => void\n}\n\nexport const ChannelHeaderRedesign: React.FC<ChannelHeaderRedesignProps> = ({\n className,\n onBack,\n showBackButton,\n showStarButton = false,\n onLeaveConversation,\n onBlockParticipant,\n showDeleteConversation = true,\n showBlockParticipant = true,\n showReportParticipant = true,\n onDeleteConversationClick,\n onBlockParticipantClick,\n onReportParticipantClick,\n customChannelActions,\n renderChannelActions,\n showChannelInfo = true,\n onParticipantNameClick,\n}) => {\n const { channel } = useChannelStateContext()\n const { client } = useChatContext()\n\n const participant = React.useMemo(\n () =>\n resolveConversationParticipant(\n channel.state?.members,\n client?.userID,\n channel.type\n ),\n [channel.state?.members, client?.userID, channel.type]\n )\n\n const participantName = resolveParticipantDisplayName(participant?.user)\n const participantImage = participant?.user?.image\n const isStarred = useChannelStar(channel)\n const isPaidCustomer =\n isCustomerPaidTag(channel.data?.customer_tag) &&\n participant?.is_follower === true\n\n const showStarBadge = showStarButton && isStarred\n const hasNameAdornment =\n isPaidCustomer || showStarBadge || Boolean(onParticipantNameClick)\n\n const handleStarClick = async () => {\n try {\n if (isStarred) {\n await (channel as ChannelType).unpin()\n } else {\n await (channel as ChannelType).pin()\n }\n } catch (error) {\n console.error(\n '[ChannelHeaderRedesign] Failed to update pinned status:',\n error\n )\n }\n }\n\n const identityContent = (\n <>\n <Avatar\n id={participant?.user?.id || channel.id || 'unknown'}\n name={participantName}\n image={participantImage}\n shape=\"circle\"\n size={48}\n className=\"-mb-2\"\n />\n <span\n className={classNames(\n 'relative z-10 flex max-w-full flex-col items-center justify-center rounded-full border border-white/10 bg-white/65 py-1.5 shadow-[0px_0px_0px_0px_rgba(0,0,0,0.04),0px_1px_2px_0px_rgba(0,0,0,0.04),0px_8px_32px_0px_rgba(0,0,0,0.1)] backdrop-blur-[2px] transition-filter duration-150 group-hover:brightness-95',\n hasNameAdornment ? 'pl-3 pr-2' : 'px-3'\n )}\n >\n <span className=\"flex max-w-full items-center justify-center\">\n <span className=\"min-w-0 truncate text-sm font-bold leading-[1.43] tracking-[0.28px] text-black/90\">\n {participantName}\n </span>\n {hasNameAdornment && (\n <span className=\"ml-2 flex gap-1\">\n {isPaidCustomer && (\n <span\n aria-label=\"Paid customer\"\n className=\"flex size-4 shrink-0 items-center justify-center rounded-full bg-[#34C759] text-white\"\n >\n <CurrencyDollarIcon className=\"size-2.5\" weight=\"bold\" />\n </span>\n )}\n {showStarBadge && (\n <span\n aria-label=\"Starred conversation\"\n className=\"flex size-4 shrink-0 items-center justify-center rounded-full bg-[#FFD60A] text-white\"\n >\n <StarIcon className=\"size-2.5\" weight=\"fill\" />\n </span>\n )}\n {onParticipantNameClick && (\n <CaretRightIcon\n aria-hidden=\"true\"\n className=\"size-4 shrink-0 text-black/65\"\n weight=\"bold\"\n />\n )}\n </span>\n )}\n </span>\n </span>\n </>\n )\n\n return (\n <div\n className={classNames(\n '@container bg-[linear-gradient(180deg,#FBFAF9_0%,rgba(251,250,249,0.92)_58%,rgba(251,250,249,0)_100%)]',\n className\n )}\n >\n <div className=\"grid min-h-[100px] grid-cols-[minmax(40px,1fr)_auto_minmax(40px,1fr)] items-start pb-2 pt-5 @lg:min-h-[104px] @lg:pt-6\">\n <div className=\"flex items-start\">\n {showBackButton && (\n <button\n className={classNames(\n ICON_BTN_CLASS,\n 'messaging-channel-view-back-button-mobile messaging-channel-view-back-button-desktop'\n )}\n onClick={onBack || (() => {})}\n type=\"button\"\n aria-label=\"Back to conversations\"\n >\n <ArrowLeftIcon className=\"size-5 text-black/90\" />\n </button>\n )}\n </div>\n\n <div className=\"flex min-w-0 justify-center\" data-dd-privacy=\"mask\">\n {onParticipantNameClick ? (\n <button\n type=\"button\"\n onClick={onParticipantNameClick}\n aria-label={`View details for ${participantName}`}\n className=\"group flex min-w-0 max-w-full appearance-none flex-col items-center border-0 bg-transparent px-2 focus-ring focus-visible:outline-none\"\n >\n {identityContent}\n </button>\n ) : (\n <div className=\"flex min-w-0 max-w-full flex-col items-center px-2\">\n {identityContent}\n </div>\n )}\n </div>\n\n <div className=\"flex items-start justify-end gap-2\">\n {showStarButton && (\n <button\n className={ICON_BTN_CLASS}\n onClick={handleStarClick}\n type=\"button\"\n aria-pressed={isStarred}\n aria-label={\n isStarred ? 'Unstar conversation' : 'Star conversation'\n }\n >\n <StarIcon\n className={classNames('size-5', {\n 'text-[#FFD60A]': isStarred,\n 'text-black/90': !isStarred,\n })}\n weight={isStarred ? 'fill' : 'regular'}\n />\n </button>\n )}\n {showChannelInfo &&\n (renderChannelActions !== undefined ? (\n renderChannelActions\n ) : (\n <ChannelActionsMenu\n channel={channel as ChannelType}\n participant={participant}\n showDeleteConversation={showDeleteConversation}\n showBlockParticipant={showBlockParticipant}\n showReportParticipant={showReportParticipant}\n onLeaveConversation={onLeaveConversation}\n onBlockParticipant={onBlockParticipant}\n onDeleteConversationClick={onDeleteConversationClick}\n onBlockParticipantClick={onBlockParticipantClick}\n onReportParticipantClick={onReportParticipantClick}\n customChannelActions={customChannelActions}\n triggerClassName={ICON_BTN_CLASS}\n />\n ))}\n </div>\n </div>\n </div>\n )\n}\n","import { DateSeparator, type DateSeparatorProps } from 'stream-chat-react'\n\nexport const CustomDateSeparator = (props: DateSeparatorProps) => (\n <DateSeparator {...props} position=\"center\" />\n)\n","import { useCallback, useMemo } from 'react'\nimport type { LocalMessage } from 'stream-chat'\nimport { useChannelStateContext, useChatContext } from 'stream-chat-react'\n\nexport type VoteSelection = 'up' | 'down' | null\n\nconst VOTE_UP = 'vote_up'\nconst VOTE_DOWN = 'vote_down'\n\nfunction getVoteFromReactions(\n ownReactions: LocalMessage['own_reactions']\n): VoteSelection {\n if (!ownReactions?.length) return null\n if (ownReactions.some((r) => r.type === VOTE_DOWN)) return 'down'\n if (ownReactions.some((r) => r.type === VOTE_UP)) return 'up'\n return null\n}\n\ninterface UseMessageVoteResult {\n selected: VoteSelection\n voteUp: () => void\n voteDown: () => void\n}\n\n/**\n * Hook that wraps Stream Chat reactions to provide toggle-style\n * upvote/downvote behavior on a message.\n *\n * Uses enforce_unique so sending a new reaction type automatically\n * removes the previous one. Uses skip_push to avoid notifying the\n * Linker on every vote.\n */\nexport function useMessageVote(message: LocalMessage): UseMessageVoteResult {\n const { channel } = useChannelStateContext()\n const { client } = useChatContext('useMessageVote')\n\n const selected = useMemo(\n () => getVoteFromReactions(message.own_reactions),\n [message.own_reactions]\n )\n\n const voteUp = useCallback(async () => {\n if (!client?.userID) return\n try {\n if (selected === 'up') {\n await channel.deleteReaction(message.id, VOTE_UP)\n } else {\n await channel.sendReaction(\n message.id,\n { type: VOTE_UP },\n { enforce_unique: true, skip_push: true }\n )\n }\n } catch {\n // Silently fail — voting is non-critical\n }\n }, [channel, client?.userID, message.id, selected])\n\n const voteDown = useCallback(async () => {\n if (!client?.userID) return\n try {\n if (selected === 'down') {\n await channel.deleteReaction(message.id, VOTE_DOWN)\n } else {\n await channel.sendReaction(\n message.id,\n { type: VOTE_DOWN },\n { enforce_unique: true, skip_push: true }\n )\n }\n } catch {\n // Silently fail — voting is non-critical\n }\n }, [channel, client?.userID, message.id, selected])\n\n return { selected, voteUp, voteDown }\n}\n","import type { LocalMessage } from 'stream-chat'\n\ntype MessageWithI18n = Pick<LocalMessage, 'text'> & {\n i18n?: Record<string, string> | null\n}\n\nexport function normalizeLanguageCode(language?: string): string | undefined {\n const normalized = language?.trim().toLowerCase().split(/[-_]/)[0]\n return normalized || undefined\n}\n\nexport function getMessageDisplayText({\n message,\n viewerLanguage,\n}: {\n message?: MessageWithI18n | null\n viewerLanguage?: string\n}): string | undefined {\n const fallbackText = message?.text\n const normalizedLanguage = normalizeLanguageCode(viewerLanguage)\n\n if (!normalizedLanguage) {\n return fallbackText\n }\n\n return message?.i18n?.[`${normalizedLanguage}_text`] ?? fallbackText\n}\n","const LINKTREE_CDN_HOSTS = ['linktr.ee']\n\nconst ATTACHMENT_IMAGE_SIZE_PRESET = 'messaging-attachment-v1_0'\n\nfunction hostnameFromUrl(url: string): string | null {\n try {\n return new URL(url).hostname.toLowerCase()\n } catch {\n return null\n }\n}\n\nfunction isLinktreeCdnHost(hostname: string): boolean {\n return LINKTREE_CDN_HOSTS.some(\n (host) => hostname === host || hostname.endsWith(`.${host}`)\n )\n}\n\nexport function optimizeMessagingAttachmentUrl(\n url: string | undefined | null\n): string | undefined {\n const trimmed = url?.trim()\n if (!trimmed) return undefined\n\n const hostname = hostnameFromUrl(trimmed)\n if (!hostname || !isLinktreeCdnHost(hostname)) return trimmed\n\n const parsedUrl = new URL(trimmed)\n const isAlreadyMessagingOptimized =\n parsedUrl.searchParams.get('io') === 'true' &&\n parsedUrl.searchParams.get('size') === ATTACHMENT_IMAGE_SIZE_PRESET\n\n if (isAlreadyMessagingOptimized) {\n return trimmed\n }\n\n parsedUrl.searchParams.set('io', 'true')\n parsedUrl.searchParams.set('size', ATTACHMENT_IMAGE_SIZE_PRESET)\n\n return parsedUrl.toString()\n}\n","export type AttachmentSourceType = 'image' | 'audio' | 'video' | 'document'\n\nexport type DocumentIconType =\n | 'pdf'\n | 'doc'\n | 'xls'\n | 'csv'\n | 'ppt'\n | 'zip'\n | 'text'\n | 'markdown'\n | 'generic'\n\nconst DOCUMENT_ICON_PATTERNS: Array<[RegExp, DocumentIconType]> = [\n [/pdf/, 'pdf'],\n [/wordprocessingml|msword|\\.doc/, 'doc'],\n [/spreadsheetml|ms-excel|\\.xls/, 'xls'],\n [/csv/, 'csv'],\n [/presentationml|ms-powerpoint|\\.ppt/, 'ppt'],\n [/zip|x-rar|x-7z|x-tar|x-gzip/, 'zip'],\n [/plain|rtf/, 'text'],\n [/markdown/, 'markdown'],\n]\n\nexport function getSourceType(mimeType: string): AttachmentSourceType {\n if (mimeType.startsWith('video/')) return 'video'\n if (mimeType.startsWith('audio/')) return 'audio'\n if (mimeType.startsWith('image/')) return 'image'\n return 'document'\n}\n\nexport function getDocumentIconType(mimeType: string): DocumentIconType {\n const match = DOCUMENT_ICON_PATTERNS.find(([pattern]) =>\n pattern.test(mimeType)\n )\n return match ? match[1] : 'generic'\n}\n","import {\n FileIcon,\n FileCsvIcon,\n FileDocIcon,\n FileMdIcon,\n FilePdfIcon,\n FilePptIcon,\n FileTextIcon,\n FileXlsIcon,\n FileZipIcon,\n ImageIcon,\n SpeakerHighIcon,\n VideoCameraIcon,\n IconProps,\n} from '@phosphor-icons/react'\nimport React from 'react'\n\nimport { getDocumentIconType, getSourceType } from './mimeType'\nimport type { AttachmentSourceType } from './mimeType'\n\nexport const MEDIA_TYPE_ICON: Record<AttachmentSourceType, React.ElementType> =\n {\n video: VideoCameraIcon,\n audio: SpeakerHighIcon,\n image: ImageIcon,\n document: FileIcon,\n }\n\nconst DOCUMENT_ICON_COMPONENT = {\n pdf: FilePdfIcon,\n doc: FileDocIcon,\n xls: FileXlsIcon,\n csv: FileCsvIcon,\n ppt: FilePptIcon,\n zip: FileZipIcon,\n text: FileTextIcon,\n markdown: FileMdIcon,\n generic: FileIcon,\n} as const\n\nexport function getTypeIcon(mimeType: string): React.ElementType {\n const sourceType = getSourceType(mimeType)\n if (sourceType !== 'document') return MEDIA_TYPE_ICON[sourceType]\n return DOCUMENT_ICON_COMPONENT[getDocumentIconType(mimeType)]\n}\n\n/** Use instead of `<TypeIcon />` where TypeIcon = getTypeIcon(mime) to satisfy react-hooks/static-components. */\nexport function renderTypeIcon(\n mimeType: string,\n props: IconProps\n): React.ReactElement {\n return React.createElement(getTypeIcon(mimeType), props)\n}\n","import { CircleNotchIcon, PauseIcon, PlayIcon } from '@phosphor-icons/react'\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\n\nimport { isDevBuild } from '../../utils/isDevBuild'\n\nimport { renderTypeIcon } from './utils/icons'\nimport { getSourceType } from './utils/mimeType'\n\ntype TouchEventUnion =\n | MouseEvent\n | TouchEvent\n | React.MouseEvent\n | React.TouchEvent\n\n\nconst getClientXFromEvent = (e: TouchEventUnion): number => {\n if ('touches' in e) {\n return e.touches[0]?.clientX ?? e.changedTouches[0]?.clientX ?? 0\n }\n return e.clientX\n}\n\nexport interface MediaPlayerProps {\n source: string\n mimeType: string\n poster?: string\n autoPlay?: boolean\n /** Controlled playing state. When provided, syncs to internal play/pause. */\n playing?: boolean\n loop?: boolean\n controls?: boolean\n showProgress?: boolean\n /** When true, requests muted playback (helps autoplay policies on video). */\n muted?: boolean\n /**\n * When provided, overrides the default click-to-play-toggle behaviour on the\n * player container. The play/pause button (which calls stopPropagation) is\n * unaffected, so inline playback still works.\n */\n onContainerClick?: (e: React.MouseEvent) => void\n}\n\nconst MediaPlayer: React.FC<MediaPlayerProps> = ({\n source,\n mimeType,\n poster,\n autoPlay = false,\n playing: playingProp,\n loop = false,\n controls = true,\n showProgress = false,\n muted = false,\n onContainerClick,\n}) => {\n // --- Derived ---\n const sourceType = getSourceType(mimeType)\n\n // --- Refs ---\n const playerRef = useRef<HTMLMediaElement>(null)\n const trackRef = useRef<HTMLDivElement>(null)\n const rafRef = useRef<number | null>(null)\n const prevPlayingPropRef = useRef(playingProp)\n\n // --- State: playback ---\n const [playing, setPlaying] = useState(autoPlay)\n const [played, setPlayed] = useState(0)\n const [seeking, setSeeking] = useState(false)\n\n // --- State: UI ---\n const [scrubberHovered, setScrubberHovered] = useState(false)\n /** Set when autoplay/play() was rejected so user can start via gesture (no controls UI). */\n const [manualPlayRequired, setManualPlayRequired] = useState(false)\n\n // --- State: loading ---\n const [buffering, setBuffering] = useState(false)\n /** True until the first canPlay fires for the current source — hides controls/spinner behind poster. */\n const [initialLoad, setInitialLoad] = useState(true)\n const [videoAspect, setVideoAspect] = useState<number | null>(null)\n\n // --- Callbacks ---\n const startPlaybackFromGesture = useCallback(() => {\n setManualPlayRequired(false)\n setPlaying(true)\n }, [])\n\n const getFraction = useCallback((e: TouchEventUnion) => {\n const track = trackRef.current\n if (!track) return 0\n const rect = track.getBoundingClientRect()\n return Math.max(\n 0,\n Math.min(1, (getClientXFromEvent(e) - rect.left) / rect.width)\n )\n }, [])\n\n const seekTo = useCallback((fraction: number) => {\n const el = playerRef.current\n if (el && el.duration) el.currentTime = fraction * el.duration\n }, [])\n\n const handleTrackPointerDown = (\n e: React.MouseEvent<HTMLDivElement> | React.TouchEvent<HTMLDivElement>\n ) => {\n e.stopPropagation()\n setSeeking(true)\n const fraction = getFraction(e)\n setPlayed(fraction)\n seekTo(fraction)\n }\n\n // --- Effects ---\n\n // Sync controlled playing prop to internal state\n useEffect(() => {\n if (\n playingProp !== undefined &&\n playingProp !== prevPlayingPropRef.current\n ) {\n prevPlayingPropRef.current = playingProp\n setPlaying(playingProp)\n }\n }, [playingProp])\n\n // RAF-driven progress updates\n useEffect(() => {\n if (!playing) {\n if (rafRef.current !== null) {\n cancelAnimationFrame(rafRef.current)\n rafRef.current = null\n }\n return\n }\n const tick = () => {\n const el = playerRef.current\n if (el && el.duration && !seeking) setPlayed(el.currentTime / el.duration)\n rafRef.current = requestAnimationFrame(tick)\n }\n rafRef.current = requestAnimationFrame(tick)\n return () => {\n if (rafRef.current !== null) cancelAnimationFrame(rafRef.current)\n }\n }, [playing, seeking])\n\n // ReactPlayer v3 uses native HTML media elements and does not support a\n // declarative `playing` prop — playback must be driven imperatively.\n useEffect(() => {\n const el = playerRef.current\n if (!el) return\n if (playing) {\n void el.play().catch((err) => {\n setPlaying(false)\n setManualPlayRequired(true)\n if (isDevBuild()) {\n console.debug('[MediaPlayer] play() failed', err)\n }\n })\n } else {\n el.pause()\n }\n }, [playing])\n\n // Global seeking listeners\n useEffect(() => {\n if (!seeking) return\n const onMove = (e: MouseEvent | TouchEvent) => setPlayed(getFraction(e))\n const onUp = (e: MouseEvent | TouchEvent) => {\n setSeeking(false)\n seekTo(getFraction(e))\n }\n window.addEventListener('mousemove', onMove)\n window.addEventListener('mouseup', onUp)\n window.addEventListener('touchmove', onMove, { passive: true })\n window.addEventListener('touchend', onUp)\n return () => {\n window.removeEventListener('mousemove', onMove)\n window.removeEventListener('mouseup', onUp)\n window.removeEventListener('touchmove', onMove)\n window.removeEventListener('touchend', onUp)\n }\n }, [seeking, getFraction, seekTo])\n\n // --- Derived render values ---\n // Use natural aspect ratio once metadata loads, fall back to 16:9 before then.\n const aspectStyle = videoAspect\n ? { aspectRatio: String(videoAspect) }\n : undefined\n const aspectClass = !videoAspect ? ' aspect-video' : ''\n const scrubberPercent = Math.round(played * 100)\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n className={`relative cursor-pointer overflow-hidden bg-black ${aspectClass}`}\n style={aspectStyle}\n onClick={(e) => {\n if (onContainerClick) { onContainerClick(e); return }\n if (manualPlayRequired) return\n if (controls) setPlaying((p) => !p)\n }}\n onKeyDown={(e) => {\n if (e.key !== 'Enter' && e.key !== ' ') return\n e.preventDefault()\n if (onContainerClick) { onContainerClick(e as unknown as React.MouseEvent); return }\n if (manualPlayRequired) return\n if (controls) setPlaying((p) => !p)\n }}\n >\n {/* For audio, poster persists as a visual background. For video, hide once loaded. */}\n {poster && (sourceType === 'audio' || initialLoad) && (\n <img\n src={poster}\n alt=\"\"\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n )}\n {!poster && (sourceType === 'audio' || initialLoad) && (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n {renderTypeIcon(mimeType, {\n className: 'size-12 text-black/20',\n weight: 'regular',\n })}\n </div>\n )}\n <div className=\"absolute inset-0\">\n {sourceType === 'audio' ? (\n <audio\n ref={playerRef as React.RefObject<HTMLAudioElement>}\n src={source}\n loop={loop}\n muted={muted}\n style={{ width: '100%', height: '100%' }}\n onLoadStart={() => setBuffering(true)}\n onCanPlay={() => {\n setBuffering(false)\n setInitialLoad(false)\n }}\n onWaiting={() => setBuffering(true)}\n onPlay={() => setManualPlayRequired(false)}\n onEnded={() => {\n if (!loop) {\n setPlaying(false)\n setPlayed(0)\n }\n }}\n >\n <track kind=\"captions\" />\n </audio>\n ) : (\n <video\n ref={playerRef as React.RefObject<HTMLVideoElement>}\n src={source}\n loop={loop}\n muted={muted}\n playsInline\n style={{ width: '100%', height: '100%' }}\n onLoadStart={() => setBuffering(true)}\n onCanPlay={() => {\n setBuffering(false)\n setInitialLoad(false)\n }}\n onWaiting={() => setBuffering(true)}\n onPlay={() => setManualPlayRequired(false)}\n onLoadedMetadata={() => {\n const el = playerRef.current\n if (\n el instanceof HTMLVideoElement &&\n el.videoWidth &&\n el.videoHeight\n ) {\n setVideoAspect(el.videoWidth / el.videoHeight)\n }\n }}\n onEnded={() => {\n if (!loop) {\n setPlaying(false)\n setPlayed(0)\n }\n }}\n >\n <track kind=\"captions\" />\n </video>\n )}\n </div>\n\n {buffering && !manualPlayRequired && (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center\">\n <CircleNotchIcon\n className=\"size-8 animate-spin text-white/80\"\n weight=\"bold\"\n />\n </div>\n )}\n\n {manualPlayRequired && !controls && (\n <div\n className=\"absolute inset-0 z-30 flex cursor-pointer items-center justify-center bg-black/35\"\n role=\"button\"\n tabIndex={0}\n aria-label=\"Play preview\"\n onClick={(e) => {\n e.stopPropagation()\n startPlaybackFromGesture()\n }}\n onKeyDown={(e) => {\n if (e.key !== 'Enter' && e.key !== ' ') return\n e.preventDefault()\n e.stopPropagation()\n startPlaybackFromGesture()\n }}\n >\n <span className=\"flex size-16 items-center justify-center rounded-full bg-white/20 text-white backdrop-blur-sm\">\n <PlayIcon className=\"size-9 translate-x-0.5\" weight=\"fill\" />\n </span>\n </div>\n )}\n\n {showProgress && !controls && (\n <div className=\"absolute inset-x-0 bottom-0 px-3 pb-2.5 pt-6 bg-gradient-to-t from-black/40 to-transparent\">\n <div\n role=\"slider\"\n aria-label=\"Playback position\"\n aria-valuenow={scrubberPercent}\n aria-valuemin={0}\n aria-valuemax={100}\n tabIndex={0}\n ref={trackRef}\n className=\"relative flex h-4 w-full cursor-pointer items-center\"\n onMouseDown={handleTrackPointerDown}\n onTouchStart={handleTrackPointerDown}\n onClick={(e) => e.stopPropagation()}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))\n if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))\n }}\n >\n <div className=\"w-full overflow-hidden rounded-full bg-white/30 h-1\">\n <div\n className=\"h-full rounded-full bg-white\"\n style={{ width: `${scrubberPercent}%` }}\n />\n </div>\n </div>\n </div>\n )}\n\n {controls && (\n <div className=\"absolute inset-x-0 bottom-0 flex items-center gap-2 bg-gradient-to-t from-black/60 to-transparent px-3 pb-2.5 pt-6 transition-all duration-200\">\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n setPlaying((p) => !p)\n }}\n className=\"shrink-0 text-white\"\n aria-label={playing ? 'Pause' : 'Play'}\n >\n {playing ? (\n <PauseIcon className=\"size-5\" weight=\"fill\" />\n ) : (\n <PlayIcon className=\"size-5 translate-x-px\" weight=\"fill\" />\n )}\n </button>\n\n <div\n role=\"slider\"\n aria-label=\"Playback position\"\n aria-valuenow={scrubberPercent}\n aria-valuemin={0}\n aria-valuemax={100}\n tabIndex={0}\n ref={trackRef}\n className=\"relative flex h-4 w-full cursor-pointer items-center\"\n onMouseDown={handleTrackPointerDown}\n onTouchStart={handleTrackPointerDown}\n onClick={(e) => e.stopPropagation()}\n onMouseEnter={() => setScrubberHovered(true)}\n onMouseLeave={() => setScrubberHovered(false)}\n onKeyDown={(e) => {\n if (e.key === 'ArrowRight') seekTo(Math.min(1, played + 0.05))\n if (e.key === 'ArrowLeft') seekTo(Math.max(0, played - 0.05))\n }}\n >\n <div\n className={`w-full overflow-hidden rounded-full bg-white/30 transition-all duration-200 ${scrubberHovered || seeking ? 'h-1.5' : 'h-1'}`}\n >\n <div\n className=\"h-full rounded-full bg-white\"\n style={{ width: `${scrubberPercent}%` }}\n />\n </div>\n <div\n className={`absolute size-3 -translate-x-1/2 rounded-full bg-white shadow transition-[opacity,transform] duration-200 ${scrubberHovered || seeking ? 'scale-100 opacity-100' : 'scale-0 opacity-0'}`}\n style={{ left: `${scrubberPercent}%` }}\n />\n </div>\n </div>\n )}\n </div>\n )\n}\n\nexport default MediaPlayer\n","import React, { useState } from 'react'\n\nimport { optimizeMessagingAttachmentUrl } from '../../utils/cdnImageUrl'\n\nimport MediaPlayer, { type MediaPlayerProps } from './MediaPlayer'\nimport { renderTypeIcon } from './utils/icons'\nimport { getSourceType } from './utils/mimeType'\n\nexport type AttachmentThumbnailVariant = 'light' | 'dark'\n\nexport interface AttachmentThumbnailProps {\n mimeType: string\n sourceUrl?: string\n thumbnailUrl?: string\n title?: string\n variant: AttachmentThumbnailVariant\n /** Extra props passed to MediaPlayer when source is video or audio. */\n mediaPlayerProps?: Partial<\n Pick<\n MediaPlayerProps,\n 'autoPlay' | 'loop' | 'muted' | 'controls' | 'onContainerClick'\n >\n >\n /**\n * When true (Visitor unlocked image/document), use aspect-video + object-contain fade-in.\n */\n containedImage?: boolean\n}\n\nconst placeholderIconClass = (variant: AttachmentThumbnailVariant) =>\n variant === 'dark' ? 'size-12 text-white/20' : 'size-12 text-black/20'\n\nconst posterShellClass = (variant: AttachmentThumbnailVariant) =>\n variant === 'dark'\n ? 'aspect-video overflow-hidden bg-white/10'\n : 'aspect-video overflow-hidden bg-black/5'\n\n/**\n * Renders the media preview area for attachment cards (LockedAttachment, MediaMessage).\n * Overlays (dim, lock, eye toggle) are composed by the parent.\n */\nconst AttachmentThumbnail: React.FC<AttachmentThumbnailProps> = ({\n mimeType,\n sourceUrl,\n thumbnailUrl,\n title,\n variant,\n mediaPlayerProps,\n containedImage = false,\n}) => {\n const sourceType = getSourceType(mimeType)\n const [sourceReady, setSourceReady] = useState(false)\n const optimizedSourceUrl = optimizeMessagingAttachmentUrl(sourceUrl)\n const optimizedThumbnailUrl = optimizeMessagingAttachmentUrl(thumbnailUrl)\n\n if (sourceUrl && (sourceType === 'video' || sourceType === 'audio')) {\n return (\n <MediaPlayer\n source={sourceUrl}\n mimeType={mimeType}\n poster={optimizedThumbnailUrl}\n controls\n {...mediaPlayerProps}\n />\n )\n }\n\n if (sourceUrl && sourceType === 'image') {\n if (containedImage) {\n return (\n <div className=\"relative aspect-video overflow-hidden bg-black/5\">\n <img\n src={optimizedSourceUrl ?? sourceUrl}\n alt={title ?? ''}\n className={`absolute inset-0 h-full w-full object-contain transition-opacity duration-300 ${sourceReady ? 'opacity-100' : 'opacity-0'}`}\n draggable={false}\n onLoad={() => setSourceReady(true)}\n />\n </div>\n )\n }\n return (\n <img\n src={optimizedSourceUrl ?? sourceUrl}\n alt={title ?? ''}\n className=\"block w-full\"\n draggable={false}\n />\n )\n }\n\n if (sourceUrl && sourceType === 'document') {\n if (thumbnailUrl) {\n if (containedImage) {\n return (\n <div className=\"relative aspect-video overflow-hidden bg-black/5\">\n <img\n src={optimizedThumbnailUrl ?? thumbnailUrl}\n alt={title ?? ''}\n className={`absolute inset-0 h-full w-full object-contain transition-opacity duration-300 ${sourceReady ? 'opacity-100' : 'opacity-0'}`}\n draggable={false}\n onLoad={() => setSourceReady(true)}\n />\n </div>\n )\n }\n return (\n <img\n src={optimizedThumbnailUrl ?? thumbnailUrl}\n alt=\"\"\n className=\"block w-full\"\n draggable={false}\n />\n )\n }\n return (\n <div\n className={`flex aspect-video w-full items-center justify-center ${variant === 'dark' ? 'bg-white/10' : 'bg-black/5'}`}\n >\n {renderTypeIcon(mimeType, {\n className: placeholderIconClass(variant),\n weight: 'regular',\n })}\n </div>\n )\n }\n\n // Poster-only or empty (no sourceUrl)\n if (thumbnailUrl) {\n return (\n <div className={`relative ${posterShellClass(variant)}`}>\n <img\n src={optimizedThumbnailUrl ?? thumbnailUrl}\n alt={title ?? ''}\n draggable={false}\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n </div>\n )\n }\n\n return (\n <div\n className={`flex aspect-video w-full items-center justify-center ${variant === 'dark' ? 'bg-white/10' : 'bg-black/5'}`}\n >\n {renderTypeIcon(mimeType, {\n className: placeholderIconClass(variant),\n weight: 'regular',\n })}\n </div>\n )\n}\n\nexport default AttachmentThumbnail\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport { renderTypeIcon } from './utils/icons'\n\nexport { default as AttachmentThumbnail } from './Thumbnail'\nexport type {\n AttachmentThumbnailProps,\n AttachmentThumbnailVariant,\n} from './Thumbnail'\nexport { default as MediaPlayer } from './MediaPlayer'\nexport type { MediaPlayerProps } from './MediaPlayer'\nexport { renderTypeIcon, getTypeIcon, MEDIA_TYPE_ICON } from './utils/icons'\nexport {\n getSourceType,\n getDocumentIconType,\n type AttachmentSourceType,\n type DocumentIconType,\n} from './utils/mimeType'\n\nexport interface AttachmentCardProps {\n variant: 'light' | 'dark'\n thumbnail: React.ReactNode\n title?: string\n placeholderTitle?: string\n mimeType: string\n detail?: string\n statusBadge?: React.ReactNode\n action?: React.ReactNode\n topLeft?: React.ReactNode\n topRight?: React.ReactNode\n rootRef?: React.Ref<HTMLDivElement>\n 'data-testid'?: string\n}\n\nconst AttachmentCard: React.FC<AttachmentCardProps> = ({\n variant,\n thumbnail,\n title,\n placeholderTitle = 'Attachment title',\n mimeType,\n detail,\n statusBadge,\n action,\n topLeft,\n topRight,\n rootRef,\n 'data-testid': dataTestId,\n}) => {\n const isDark = variant === 'dark'\n const displayTitle = isDark ? (title ?? placeholderTitle) : (title ?? '')\n const titleDimmed = isDark && !title\n\n return (\n <div\n ref={rootRef}\n data-testid={dataTestId}\n className={classNames(\n 'relative w-[280px] select-none overflow-hidden rounded-[24px] shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]',\n isDark ? 'bg-[#1e2330]' : 'bg-white'\n )}\n >\n {topLeft ? (\n <div className=\"pointer-events-auto absolute left-3 top-3 z-50\">{topLeft}</div>\n ) : null}\n {topRight ? (\n <div className=\"pointer-events-auto absolute right-3 top-3 z-50\">{topRight}</div>\n ) : null}\n\n {thumbnail}\n\n <div className=\"px-4 pb-3 pt-3\">\n {displayTitle.trim() !== '' && (\n <p\n className={classNames('mb-0.5 truncate text-base font-medium', {\n 'text-black': !isDark,\n 'text-white/30': isDark && titleDimmed,\n 'text-white': isDark && !titleDimmed,\n })}\n >\n {displayTitle}\n </p>\n )}\n\n <div className=\"flex flex-wrap items-center gap-1\">\n {renderTypeIcon(mimeType, {\n className: classNames(\n 'size-5 shrink-0',\n isDark ? 'text-white/55' : 'text-black/55'\n ),\n weight: 'regular',\n })}\n\n {detail != null && detail !== '' && (\n <span\n className={classNames(\n 'text-xs font-medium',\n isDark ? 'text-white/55' : 'text-black/55'\n )}\n >\n {detail}\n </span>\n )}\n\n {statusBadge}\n </div>\n\n {action}\n </div>\n </div>\n )\n}\n\nexport default AttachmentCard\n","import { CircleNotchIcon, DownloadSimpleIcon, LinkIcon } from '@phosphor-icons/react'\nimport React, { useState } from 'react'\nimport type { Attachment, LocalMessage } from 'stream-chat'\n\nimport { optimizeMessagingAttachmentUrl } from '../../utils/cdnImageUrl'\nimport AttachmentCard, {\n AttachmentThumbnail,\n getSourceType,\n} from '../AttachmentCard'\nimport { Avatar } from '../Avatar'\n\nfunction formatBytes(bytes: number): string {\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`\n}\n\nfunction linkCardShellClass(isMyMessage: boolean) {\n const bg = isMyMessage ? 'bg-[#1e2330]' : 'bg-[#F3F3F1]'\n return `w-[280px] select-none overflow-hidden rounded-[24px] ${bg} shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_4px_8px_rgba(0,0,0,0.06)]`\n}\n\nfunction linkThumbnailBg(isMyMessage: boolean) {\n return isMyMessage ? 'bg-white/10' : 'bg-black/5'\n}\n\nfunction linkPrimaryText(isMyMessage: boolean) {\n return isMyMessage ? 'text-white' : 'text-black'\n}\n\nfunction linkSecondaryText(isMyMessage: boolean) {\n return isMyMessage ? 'text-white/55' : 'text-black/55'\n}\n\nfunction linkTertiaryText(isMyMessage: boolean) {\n return isMyMessage ? 'text-white/40' : 'text-black/40'\n}\n\nfunction linkIconColor(isMyMessage: boolean) {\n return isMyMessage ? 'text-white/20' : 'text-black/20'\n}\n\n/** Link preview (OG) — no download; opens in a new tab. */\nconst LinkCard: React.FC<{\n attachment: Attachment\n isMyMessage: boolean\n}> = ({ attachment, isMyMessage }) => {\n const { title, text, image_url, og_scrape_url, title_link } = attachment\n const rawUrl = og_scrape_url ?? title_link\n const url =\n typeof rawUrl === 'string' && rawUrl.trim() !== '' ? rawUrl : undefined\n\n const body = (\n <React.Fragment>\n <div className=\"p-2\">\n {image_url ? (\n <img\n src={optimizeMessagingAttachmentUrl(image_url) ?? image_url}\n alt={title ?? ''}\n className=\"aspect-video w-full rounded-[20px] object-cover\"\n />\n ) : (\n <div\n className={`aspect-video w-full rounded-[20px] ${linkThumbnailBg(isMyMessage)} flex items-center justify-center`}\n >\n <LinkIcon className={`size-12 ${linkIconColor(isMyMessage)}`} />\n </div>\n )}\n </div>\n <div className=\"px-3 pb-3\">\n {title && (\n <p className={`truncate text-[14px] font-medium leading-5 ${linkPrimaryText(isMyMessage)}`}>\n {title}\n </p>\n )}\n {text && (\n <p className={`truncate text-[12px] leading-4 ${linkSecondaryText(isMyMessage)}`}>{text}</p>\n )}\n {url && (\n <p className={`mt-1 truncate text-[12px] leading-4 ${linkTertiaryText(isMyMessage)}`}>\n {url}\n </p>\n )}\n </div>\n </React.Fragment>\n )\n\n if (url) {\n return (\n <a href={url} target=\"_blank\" rel=\"noopener noreferrer\" className=\"block no-underline\">\n {body}\n </a>\n )\n }\n\n return <div className=\"block\">{body}</div>\n}\n\nexport function isLinkAttachment(a: Attachment): boolean {\n return a.type === 'link' || (!!a.og_scrape_url && !a.asset_url)\n}\n\nexport function resolveLinkAttachment(\n message: LocalMessage\n): Attachment | undefined {\n return message.attachments?.find(isLinkAttachment)\n}\n\nasync function triggerDownload(url: string, filename?: string): Promise<void> {\n let name: string\n try { name = filename ?? new URL(url).pathname.split('/').pop() ?? 'download' }\n catch { name = filename ?? 'download' }\n const res = await fetch(url, { mode: 'cors' })\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const blob = await res.blob()\n const objectUrl = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = objectUrl\n a.download = name\n a.style.display = 'none'\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n URL.revokeObjectURL(objectUrl)\n}\n\nconst DownloadAction: React.FC<{ url: string; filename?: string }> = ({\n url,\n filename,\n}) => {\n const [busy, setBusy] = useState(false)\n\n const handleClick = (e: React.MouseEvent) => {\n e.stopPropagation()\n const fallback = window.open('', '_blank', 'noopener,noreferrer')\n setBusy(true)\n triggerDownload(url, filename)\n .then(() => { fallback?.close() })\n .catch(() => { if (fallback) fallback.location.href = url })\n .finally(() => setBusy(false))\n }\n\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={busy}\n className=\"mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full bg-[#121110] px-4 text-sm font-medium leading-none text-white hover:bg-[#2a2928] disabled:opacity-70\"\n >\n {busy ? (\n <CircleNotchIcon className=\"size-4 animate-spin text-white\" weight=\"bold\" />\n ) : (\n <React.Fragment>\n <DownloadSimpleIcon className=\"size-4 text-white\" weight=\"bold\" />\n Download\n </React.Fragment>\n )}\n </button>\n )\n}\n\nexport interface MediaMessageResolved {\n resolvedUrl: string\n resolvedType: string\n title?: string\n fileSize?: number\n thumbnailUrl?: string\n}\n\nexport function resolveMediaFromMessage(\n message: LocalMessage\n): MediaMessageResolved | null {\n const videoAttachment = message.attachments?.find(\n (a) => a.type === 'video' && a.asset_url\n )\n const imageAttachment = message.attachments?.find(\n (a) => a.type === 'image' && (a as { image_url?: string }).image_url\n )\n const audioAttachment = message.attachments?.find(\n (a) => a.type === 'audio' && a.asset_url\n )\n const fileAttachment = message.attachments?.find(\n (a) => a.type === 'file' && a.asset_url\n )\n\n const activeAttachment =\n videoAttachment ?? imageAttachment ?? audioAttachment ?? fileAttachment\n\n const resolvedUrl =\n videoAttachment?.asset_url ??\n (imageAttachment as { image_url?: string } | undefined)?.image_url ??\n audioAttachment?.asset_url ??\n fileAttachment?.asset_url\n\n if (!resolvedUrl) return null\n\n const resolvedType =\n activeAttachment?.mime_type ??\n (activeAttachment?.type === 'image'\n ? 'image/jpeg'\n : activeAttachment?.type === 'video'\n ? 'video/mp4'\n : activeAttachment?.type === 'audio'\n ? 'audio/mpeg'\n : 'application/octet-stream')\n\n const title = (activeAttachment as { title?: string } | undefined)?.title\n const fileSize = (activeAttachment as { file_size?: number } | undefined)?.file_size\n const thumbnailUrl = (videoAttachment as { thumb_url?: string } | undefined)?.thumb_url\n\n return {\n resolvedUrl,\n resolvedType,\n title,\n fileSize,\n thumbnailUrl,\n }\n}\n\n/** Dark card (sent / own message) — matches LockedAttachment.Sent preview without toggle. */\nconst Creator: React.FC<MediaMessageResolved> = ({\n resolvedUrl,\n resolvedType,\n title,\n fileSize,\n thumbnailUrl,\n}) => {\n const detail = fileSize !== undefined ? formatBytes(fileSize) : undefined\n\n return (\n <AttachmentCard\n variant=\"dark\"\n title={title}\n placeholderTitle=\"\"\n mimeType={resolvedType}\n detail={detail}\n thumbnail={\n <AttachmentThumbnail\n mimeType={resolvedType}\n sourceUrl={resolvedUrl}\n thumbnailUrl={thumbnailUrl}\n title={title}\n variant=\"dark\"\n />\n }\n />\n )\n}\n\n/** Light card (received) — matches LockedAttachment.Received unlocked without Purchased copy. */\nconst Visitor: React.FC<MediaMessageResolved> = ({\n resolvedUrl,\n resolvedType,\n title,\n fileSize,\n thumbnailUrl,\n}) => {\n const sourceType = getSourceType(resolvedType)\n const detail = fileSize !== undefined ? formatBytes(fileSize) : undefined\n\n return (\n <AttachmentCard\n variant=\"light\"\n title={title}\n mimeType={resolvedType}\n detail={detail}\n thumbnail={\n <AttachmentThumbnail\n mimeType={resolvedType}\n sourceUrl={resolvedUrl}\n thumbnailUrl={thumbnailUrl}\n title={title}\n variant=\"light\"\n containedImage={sourceType === 'image' || sourceType === 'document'}\n />\n }\n action={<DownloadAction url={resolvedUrl} filename={title} />}\n />\n )\n}\n\nexport interface MediaMessageProps {\n message: LocalMessage\n isMyMessage?: boolean\n}\n\n/**\n * @deprecated Slated for removal once the profile repo is migrated to\n * `LinkAttachment` (and its forthcoming media-only Attachment variants).\n * New consumers should reach for `LinkAttachment.Composer`,\n * `LinkAttachment.Sent`, or `LinkAttachment.Received` instead.\n */\nconst MediaMessageRoot: React.FC<MediaMessageProps> = ({\n message,\n isMyMessage = false,\n}) => {\n const linkAttachment = resolveLinkAttachment(message)\n const resolved = resolveMediaFromMessage(message)\n if (!linkAttachment && !resolved) return null\n\n const messageClass = isMyMessage\n ? 'str-chat__message str-chat__message-simple str-chat__message--me str-chat__message-simple--me'\n : 'str-chat__message str-chat__message-simple str-chat__message--other'\n\n return (\n <div className={messageClass} data-dd-privacy=\"mask\">\n {!isMyMessage && message.user && (\n <Avatar\n className=\"str-chat__avatar str-chat__message-sender-avatar\"\n id={message.user.id}\n image={message.user.image}\n name={message.user.name ?? message.user.id}\n />\n )}\n <div\n className=\"str-chat__message-inner\"\n style={{ marginInlineEnd: 0, marginInlineStart: 0 }}\n >\n <div className=\"str-chat__message-bubble-wrapper\">\n <div\n className=\"str-chat__message-bubble\"\n // The visible child is a self-contained AttachmentCard (white /\n // #1e2330 card with its own shadow + radius), not a speech\n // bubble, and this wrapper is transparent. Mark it so the\n // received/sent tail in `styles.css` skips it — otherwise a\n // tail tinted from the text-bubble surface (#f2f1ef) hangs off\n // a white card and reads as a detached grey nub.\n data-attachment-bubble=\"true\"\n style={{ padding: 0, borderRadius: 0, overflow: 'visible', background: 'transparent' }}\n >\n {linkAttachment ? (\n <div className={linkCardShellClass(isMyMessage)}>\n <LinkCard attachment={linkAttachment} isMyMessage={isMyMessage} />\n </div>\n ) : isMyMessage ? (\n <Creator {...resolved!} />\n ) : (\n <Visitor {...resolved!} />\n )}\n </div>\n </div>\n </div>\n </div>\n )\n}\n\nconst MediaMessageCreatorEntry: React.FC<{ message: LocalMessage }> = ({\n message,\n}) => {\n const linkAttachment = resolveLinkAttachment(message)\n if (linkAttachment) {\n return (\n <div className={linkCardShellClass(true)} data-dd-privacy=\"mask\">\n <LinkCard attachment={linkAttachment} isMyMessage={true} />\n </div>\n )\n }\n const resolved = resolveMediaFromMessage(message)\n if (!resolved) return null\n return (\n <div style={{ display: 'contents' }} data-dd-privacy=\"mask\">\n <Creator {...resolved} />\n </div>\n )\n}\n\nconst MediaMessageVisitorEntry: React.FC<{ message: LocalMessage }> = ({\n message,\n}) => {\n const linkAttachment = resolveLinkAttachment(message)\n if (linkAttachment) {\n return (\n <div className={linkCardShellClass(false)} data-dd-privacy=\"mask\">\n <LinkCard attachment={linkAttachment} isMyMessage={false} />\n </div>\n )\n }\n const resolved = resolveMediaFromMessage(message)\n if (!resolved) return null\n return (\n <div style={{ display: 'contents' }} data-dd-privacy=\"mask\">\n <Visitor {...resolved} />\n </div>\n )\n}\n\nexport const MediaMessage = Object.assign(MediaMessageRoot, {\n Creator: MediaMessageCreatorEntry,\n Visitor: MediaMessageVisitorEntry,\n})\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport type { BubbleGroupPosition, BubbleVariant } from '../types'\n\nexport interface BubbleProps {\n variant: BubbleVariant\n /** Optional message text rendered below the attachment slot. */\n text?: React.ReactNode\n /**\n * Renders a hairline border around the bubble. Defaults to `true` —\n * matches the design system's \"small border around link / message\n * attachments\" treatment from the mobile spec.\n */\n bordered?: boolean\n /**\n * Position of this bubble inside a same-author message run. Drives\n * the corner-flattening that visually merges consecutive bubbles\n * (matches the grouping stream-chat-react applies to text bubbles).\n * Defaults to `'single'` so standalone usage keeps every corner\n * fully rounded.\n */\n groupPosition?: BubbleGroupPosition\n /**\n * Width utility that REPLACES the default `'w-[280px]'`. Supply the\n * whole declaration (`'w-fit'`, `'w-[383px] max-w-full'`) — nothing\n * is merged with the default.\n */\n widthClassName?: string\n /**\n * Padding utility that REPLACES the default `'px-2 py-2'`. Supply\n * the full inset (`'p-0.5'`, `'py-5 pl-5 pr-3'`).\n */\n paddingClassName?: string\n /**\n * Corner utility that REPLACES the `groupPosition`-derived corner\n * table entirely. Opting in means opting OUT of same-author corner\n * flattening — the bubble renders the supplied radius on all four\n * corners. That is what the attachment redesign wants: Figma draws\n * media cards at a uniform r20 even when a caption bubble sits 4px\n * below (nodes `1973:12776`, `1973:20239`).\n *\n * It replaces rather than appends because appending cannot be made\n * reliable — an appended `rounded-[20px]` and the corner table have\n * identical CSS specificity, so Tailwind's emitted source order,\n * not argument order, would pick the winner.\n */\n radiusClassName?: string\n className?: string\n children: React.ReactNode\n 'data-testid'?: string\n}\n\n// Colors and metrics tracked from `stream-chat-react`'s default\n// `.str-chat__message-bubble` token set so attachments visually drop\n// into a normal `CustomMessage` thread without any seams:\n// - light → `--str-chat__message-bubble-background-color` in `styles.css` (#f2f1ef)\n// - dark → `.str-chat__message--me` override in `styles.css` (#1e2330)\n// - text padding → `--str-chat__spacing-2 --str-chat__spacing-4` = 8px 16px\n// - border-radius → `--str-chat__border-radius-md` = 18px\n//\n// TODO: migrate to Tailwind theme tokens once they exist for the\n// `linktree.dark` / `linktree.surface.secondary` palette — these hex\n// literals are repeated across LinkAttachment / LockedAttachment /\n// MediaMessage / CustomMessageInput / DownloadAction etc., so a\n// rebrand would need to touch every site. A central token would\n// collapse the migration to one definition.\nconst BUBBLE_BG_BY_VARIANT: Record<BubbleVariant, string> = {\n dark: 'bg-[#1e2330]',\n light: 'bg-[#f2f1ef]',\n}\n\nconst BUBBLE_TEXT_BY_VARIANT: Record<BubbleVariant, string> = {\n dark: 'text-white',\n light: 'text-[#000000]',\n}\n\nconst BUBBLE_BORDER_BY_VARIANT: Record<BubbleVariant, string> = {\n dark: 'border-white/[0.08]',\n light: 'border-black/[0.08]',\n}\n\n/**\n * Per-corner rounding tables that mirror stream-chat-react's default\n * bubble grouping. Sender bubbles cluster against the right edge so\n * the right corners get flattened in a run; receiver bubbles cluster\n * against the left edge so the left corners get flattened.\n *\n * Rounded corners use `--str-chat__border-radius-md` (18px) and\n * flattened corners use `--str-chat__border-radius-sm` (4px) — same\n * tokens stream's stylesheet uses for `.str-chat__message-bubble`\n * inside a `--first` / `--group` / `--end` wrapper.\n */\ntype BubbleSide = 'sender' | 'receiver'\n\nconst sideForVariant = (variant: BubbleVariant): BubbleSide =>\n variant === 'dark' ? 'sender' : 'receiver'\n\nconst CORNER_CLASSES_BY_SIDE_AND_POSITION: Record<\n BubbleSide,\n Record<BubbleGroupPosition, string>\n> = {\n sender: {\n single:\n 'rounded-tl-[18px] rounded-tr-[18px] rounded-bl-[18px] rounded-br-[18px]',\n first:\n 'rounded-tl-[18px] rounded-tr-[18px] rounded-bl-[18px] rounded-br-[4px]',\n middle:\n 'rounded-tl-[18px] rounded-tr-[4px] rounded-bl-[18px] rounded-br-[4px]',\n end: 'rounded-tl-[18px] rounded-tr-[4px] rounded-bl-[18px] rounded-br-[18px]',\n },\n receiver: {\n single:\n 'rounded-tl-[18px] rounded-tr-[18px] rounded-bl-[18px] rounded-br-[18px]',\n first:\n 'rounded-tl-[18px] rounded-tr-[18px] rounded-bl-[4px] rounded-br-[18px]',\n middle:\n 'rounded-tl-[4px] rounded-tr-[18px] rounded-bl-[4px] rounded-br-[18px]',\n end: 'rounded-tl-[4px] rounded-tr-[18px] rounded-bl-[18px] rounded-br-[18px]',\n },\n}\n\n/**\n * Chat-bubble container shared by every `MessageAttachment.*` card.\n *\n * Holds the attachment slot (image / video / row) and an optional\n * `text` caption rendered below it — mirrors the mobile chat layout\n * where a sender can attach a caption alongside an attachment ('Here\n * is the file', 'Here is the image').\n *\n * Two visual variants:\n * - `dark` — sender-side (Composer / Sent), white text on `#1e2330`.\n * - `light` — recipient-side (Received), dark text on `#f2f1ef`.\n *\n * Background colors, padding, border-radius, and inherited font come\n * from the stream-chat-react `.str-chat__message-bubble` token set so\n * attachments line up with the `CustomMessage` text bubbles in a real\n * conversation. Padding is uniform across every variant — text rows,\n * audio rows, document rows, and media bubbles all get the same\n * 8px / 16px inset, with media inside getting its own rounded corners.\n */\nconst Bubble: React.FC<BubbleProps> = ({\n variant,\n text,\n bordered = true,\n groupPosition = 'single',\n widthClassName = 'w-[280px]',\n paddingClassName = 'px-2 py-2',\n radiusClassName,\n className,\n children,\n 'data-testid': dataTestId,\n}) => {\n const hasText = text != null && text !== ''\n const cornerClasses =\n radiusClassName ??\n CORNER_CLASSES_BY_SIDE_AND_POSITION[sideForVariant(variant)][groupPosition]\n\n return (\n <div\n data-testid={dataTestId}\n data-group-position={groupPosition}\n className={classNames(\n // Defaults describe a 280px-wide document / audio / link\n // bubble: that width matches the mobile chat attachment and\n // the 8px / 16px inset matches `--str-chat__spacing-2\n // --str-chat__spacing-4`, so attachments share the same hit /\n // negative-space rhythm as the surrounding `CustomMessage`\n // text bubbles. Media cards override both — Figma sizes them\n // to the media (330 / 383px) with a 2px frame instead.\n 'relative',\n widthClassName,\n 'overflow-hidden',\n paddingClassName,\n // The caption below owns the bubble's whole bottom inset when\n // it is present, so the container hands it over. Without this\n // the inset is whatever `paddingClassName` happens to supply,\n // which is 2px for the media cards' `p-0.5` frame and leaves\n // caption text sitting on the card edge. Safe to append: this\n // is shorthand-then-longhand (`p-*`/`py-*` then `pb-*`), which\n // Tailwind always emits in that order inside its `padding`\n // plugin — unlike the same-utility `rounded-*` clash above,\n // there is no ambiguity to resolve.\n hasText && 'pb-0',\n cornerClasses,\n BUBBLE_BG_BY_VARIANT[variant],\n BUBBLE_TEXT_BY_VARIANT[variant],\n bordered && 'border',\n bordered && BUBBLE_BORDER_BY_VARIANT[variant],\n className\n )}\n >\n {children}\n\n {hasText ? (\n <p\n className={classNames(\n // No `text-*` / `font-*` overrides here — caption inherits\n // the same font family + size as `.str-chat__message-text`\n // so it matches the surrounding `CustomMessage` bubbles.\n 'whitespace-pre-wrap break-words leading-snug',\n // Owns the full vertical inset: an 8px gutter above (the\n // children render flush against their own bottom edge)\n // and the bubble's 8px bottom inset below, which the\n // container gave up via `pb-0`. Horizontally this stacks\n // on the container's inset so a default bubble's caption\n // lands at the 16px `--str-chat__spacing-4` text inset.\n 'pb-2 pt-2',\n 'px-2'\n )}\n >\n {text}\n </p>\n ) : null}\n </div>\n )\n}\n\nexport default Bubble\n","import { XIcon } from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React from 'react'\n\nexport interface DismissButtonProps {\n onClick: () => void\n /**\n * `'overlay'` — translucent button sitting on top of media (image /\n * video corner). `'inline'` — opaque button placed in the row of a\n * compact attachment alongside other content.\n */\n variant?: 'overlay' | 'inline'\n ariaLabel?: string\n}\n\nconst DismissButton: React.FC<DismissButtonProps> = ({\n onClick,\n variant = 'overlay',\n ariaLabel = 'Dismiss attachment',\n}) => (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n onClick()\n }}\n aria-label={ariaLabel}\n className={classNames(\n 'flex size-6 items-center justify-center rounded-full text-white',\n variant === 'overlay'\n ? 'bg-[#121110]/85 backdrop-blur'\n : 'bg-white/15 hover:bg-white/25'\n )}\n >\n <XIcon className=\"size-3\" weight=\"bold\" />\n </button>\n)\n\nexport default DismissButton\n","import type React from 'react'\n\n/**\n * Three messaging states a `MessageAttachment.*` card can render in,\n * mirroring the LinkAttachment / LockedAttachment families.\n */\nexport type MessageAttachmentState = 'composer' | 'sent' | 'received'\n\n/**\n * Visual variant of the surrounding `Bubble` chrome. Sender-side bubbles\n * (`composer`, `sent`) get the dark treatment; recipient-side bubbles\n * (`received`) get the light treatment.\n */\nexport type BubbleVariant = 'dark' | 'light'\n\n/** Maps a state to the bubble chrome it should render with. */\nexport const bubbleVariantForState = (\n state: MessageAttachmentState\n): BubbleVariant => (state === 'received' ? 'light' : 'dark')\n\n/**\n * Position of a bubble inside a same-author run, mirroring the grouping\n * stream-chat-react applies to consecutive messages from the same user:\n *\n * - `'single'` — standalone message, every corner fully rounded.\n * - `'first'` — first in a 2+ message run; the corner facing the\n * next bubble in the run is flattened.\n * - `'middle'` — both the corner facing the previous bubble and the\n * corner facing the next bubble are flattened.\n * - `'end'` — last in a 2+ message run; the corner facing the\n * previous bubble is flattened (this is also the\n * bubble the avatar attaches to on the receiver side).\n *\n * \"Facing\" is determined by the bubble's side: sender-aligned bubbles\n * cluster against their right edge, receiver-aligned bubbles against\n * their left edge.\n */\nexport type BubbleGroupPosition = 'single' | 'first' | 'middle' | 'end'\n\n/**\n * Convenience adapter that derives the `BubbleGroupPosition` from the\n * three booleans `stream-chat-react` forwards into the `Message` HOC.\n * Drop-in for `useMessageContext()` consumers:\n *\n * ```tsx\n * const { firstOfGroup, endOfGroup, groupedByUser } = useMessageContext()\n * <MessageAttachment.Image.Sent\n * {...props}\n * groupPosition={bubbleGroupPositionFromStream({\n * firstOfGroup, endOfGroup, groupedByUser,\n * })}\n * />\n * ```\n */\nexport const bubbleGroupPositionFromStream = ({\n firstOfGroup,\n endOfGroup,\n groupedByUser,\n}: {\n firstOfGroup?: boolean\n endOfGroup?: boolean\n groupedByUser?: boolean\n}): BubbleGroupPosition => {\n if (!groupedByUser) return 'single'\n if (firstOfGroup && endOfGroup) return 'single'\n if (firstOfGroup) return 'first'\n if (endOfGroup) return 'end'\n return 'middle'\n}\n\n/** Loading hint forwarded to the underlying `<img>` element. */\nexport type ImageLoadingMode = 'lazy' | 'eager'\n\n/** Preload hint forwarded to the underlying `<video>` / `<audio>` element. */\nexport type MediaPreloadMode = 'none' | 'metadata' | 'auto'\n\n/** A single image inside an Image attachment (single or stacked). */\nexport interface ImageItem {\n src: string\n alt?: string\n /** Optional width/height hint — preserved aspect on cover-fit. */\n width?: number\n height?: number\n /**\n * Per-tile native lazy-load override. Defaults to the parent\n * attachment's `loading` value (which itself defaults to `'lazy'`).\n * Pass `'eager'` for above-the-fold hero tiles.\n */\n loading?: ImageLoadingMode\n}\n\n/** A single video inside a Video attachment (single or stacked). */\nexport interface VideoItem {\n src: string\n /** Poster / thumbnail rendered before playback starts. */\n poster?: string\n /** Optional MIME type (e.g. `video/mp4`). */\n mimeType?: string\n /**\n * Per-item override for the `<video preload>` hint. Defaults to the\n * parent attachment's `preload` value (which itself defaults to\n * `'none'` so the poster carries the visual weight and no video\n * bytes are fetched until playback).\n */\n preload?: MediaPreloadMode\n}\n\n/** A single PDF inside a Pdf attachment (single or stacked). */\nexport interface PdfItem {\n src: string\n /** Filename — drives the row title + the meta line + the viewer dialog's accessible name. */\n filename?: string\n /** File size in bytes — formatted into the `EXT · SIZE` meta line. */\n fileSize?: number\n /** Override displayed title. Defaults to `filename`. */\n title?: string\n}\n\n/** A single audio file inside an Audio attachment (single or stacked). */\nexport interface AudioItem {\n src: string\n /** MIME type hint — typed onto the inline `<source>` element. */\n mimeType?: string\n /**\n * Filename — used as the download default name (consumed by the\n * native player's kebab menu).\n */\n filename?: string\n /**\n * Per-track override for the `<audio preload>` hint. Defaults to\n * the parent attachment's `preload` value, which itself defaults\n * to `'metadata'` for a single audio (so the native player can\n * surface duration immediately) and `'none'` for a stacked thread\n * (so a fan-out of metadata requests doesn't happen on first paint).\n */\n preload?: MediaPreloadMode\n}\n\n/** A single generic file inside a File attachment (single or stacked). */\nexport interface FileItem {\n src: string\n /** Filename — drives the title + the meta line + the download default name. */\n filename?: string\n /** File size in bytes — formatted into the `EXT · SIZE` meta line. */\n fileSize?: number\n /** MIME type — drives the type icon. Defaults to `application/octet-stream`. */\n mimeType?: string\n /** Override displayed title. Defaults to `filename`. */\n title?: string\n}\n\n/** Shared props every `MessageAttachment.*.{Composer|Sent|Received}` accepts. */\nexport interface MessageAttachmentBaseProps {\n /**\n * Optional text rendered below the attachment inside the same bubble.\n * Mirrors the chat behavior in the mobile screenshots — `'Here is the\n * file'`, `'Here is the image'`, etc.\n */\n text?: React.ReactNode\n /**\n * Position of this attachment inside a same-author message run —\n * mirrors the corner-flattening stream-chat-react applies to text\n * bubbles in a clustered conversation. Defaults to `'single'`, so\n * standalone usage keeps every corner rounded. Pass the value\n * derived from `bubbleGroupPositionFromStream({ firstOfGroup,\n * endOfGroup, groupedByUser })` when rendering inside a real\n * `CustomMessage` thread to make the attachment visually merge\n * with adjacent bubbles. Image / Video Composer ignore this — the\n * draft preview renders bare without the surrounding bubble.\n */\n groupPosition?: BubbleGroupPosition\n}\n\n/** Shared props for the `Composer` state of every attachment type. */\nexport interface ComposerExtras {\n /** Renders a dismiss `×` button overlaid on the attachment. */\n onDismiss?: () => void\n}\n","import React from 'react'\n\nimport Bubble from '../_shared/Bubble'\nimport DismissButton from '../_shared/DismissButton'\nimport {\n bubbleVariantForState,\n type AudioItem,\n type ComposerExtras,\n type MediaPreloadMode,\n type MessageAttachmentBaseProps,\n type MessageAttachmentState,\n} from '../types'\n\nexport interface AudioAttachmentSharedProps extends MessageAttachmentBaseProps {\n /** Audio source URL (`mp3`, `aac`, `wav`, …). */\n src?: string\n /** MIME type hint — typed onto the inline `<source>` element. */\n mimeType?: string\n /**\n * Filename — used as the download default name (consumed by the\n * native player's kebab menu). The HTML `<audio>` element doesn't\n * expose a built-in title slot, so we don't render the filename\n * inside the bubble itself.\n */\n filename?: string\n /**\n * Stacked audio. Takes precedence over `src` when set. Each item\n * renders its own native `<audio controls>` player, vertically\n * stacked inside the same bubble with an 8px gap between players.\n * Sent + Received only — the composer surface accepts a single\n * attachment at a time.\n */\n items?: AudioItem[]\n /**\n * `<audio preload>` hint applied to every player on the bubble.\n * When omitted, the default depends on the rendered shape:\n *\n * - Single audio (no `items`, or `items.length === 1`) →\n * `'metadata'` so the native player surfaces duration\n * immediately.\n * - Stacked audio (`items.length > 1`) → `'none'` so a thread\n * of voice memos doesn't fan out N parallel metadata requests\n * on first paint.\n *\n * Per-track overrides live on `AudioItem.preload`.\n */\n preload?: MediaPreloadMode\n}\n\nconst resolveItems = ({\n src,\n mimeType,\n filename,\n items,\n}: {\n src?: string\n mimeType?: string\n filename?: string\n items?: AudioItem[]\n}): AudioItem[] => {\n if (items && items.length > 0) return items\n if (src) return [{ src, mimeType, filename }]\n return []\n}\n\nconst NativeAudioPlayer: React.FC<{\n item: AudioItem\n preload: MediaPreloadMode\n trailingAction?: React.ReactNode\n}> = ({ item, preload, trailingAction }) => (\n <div className=\"flex items-center gap-2\">\n {/* No `<track>` is rendered — we don't author caption sidecars\n for chat attachments, and an empty `<track kind=\"captions\" />`\n emits a runtime warning. Re-add a real track (with `src`,\n `srcLang`, and `default`) once caption support actually ships.\n The `jsx-a11y/media-has-caption` rule is suppressed for the\n same reason. */}\n {/* eslint-disable-next-line jsx-a11y/media-has-caption */}\n <audio\n src={item.src}\n controls\n preload={item.preload ?? preload}\n className=\"block min-w-0 flex-1\"\n >\n {item.mimeType ? <source src={item.src} type={item.mimeType} /> : null}\n </audio>\n {trailingAction ?? null}\n </div>\n)\n\ninterface InternalAudioRowProps extends AudioAttachmentSharedProps {\n state: MessageAttachmentState\n onDismiss?: () => void\n}\n\nconst AudioAttachmentRow: React.FC<InternalAudioRowProps> = ({\n state,\n src,\n mimeType,\n filename,\n items,\n text,\n groupPosition,\n preload,\n onDismiss,\n}) => {\n const variant = bubbleVariantForState(state)\n const showDismiss = state === 'composer' && !!onDismiss\n const resolvedItems = resolveItems({ src, mimeType, filename, items })\n\n if (resolvedItems.length === 0) {\n return null\n }\n\n // Default rule: surface duration immediately for a single player,\n // but avoid fanning out N metadata requests when the bubble is a\n // thread of voice memos. An explicit `preload` prop overrides;\n // per-track overrides live on `AudioItem.preload`.\n const resolvedPreload: MediaPreloadMode =\n preload ?? (resolvedItems.length > 1 ? 'none' : 'metadata')\n\n return (\n <Bubble\n variant={variant}\n text={text}\n groupPosition={groupPosition}\n data-testid=\"audio-attachment\"\n >\n {/* Native `<audio controls>` already exposes a download in its\n kebab menu, so we don't render a separate Download button.\n The element also has no built-in title slot — the dismiss\n button (Composer only) sits inline next to the player.\n Stacked players get an 8px vertical gap so each track reads\n as a discrete attachment. */}\n <div className=\"flex flex-col gap-2\">\n {resolvedItems.map((item, index) => (\n <NativeAudioPlayer\n key={`${item.src}-${index}`}\n item={item}\n preload={resolvedPreload}\n trailingAction={\n // Composer only supports a single attachment, so the\n // dismiss control sits on the only player.\n showDismiss && index === 0 ? (\n <DismissButton onClick={onDismiss!} variant=\"inline\" />\n ) : undefined\n }\n />\n ))}\n </div>\n </Bubble>\n )\n}\n\n/**\n * Composer-only props. Single audio (`src`) only — the composer\n * surface accepts a single attachment at a time, so `items` is not\n * supported. Captions (`text`) and `groupPosition` are also dropped:\n * the composer renders a standalone draft, not part of a same-author\n * run, and captions live in the message-input textarea, not inside\n * the draft attachment preview.\n */\nexport interface AudioComposerProps extends ComposerExtras {\n src?: string\n mimeType?: string\n filename?: string\n /** See `AudioAttachmentSharedProps.preload`. */\n preload?: MediaPreloadMode\n}\nexport type AudioSentProps = AudioAttachmentSharedProps\nexport type AudioReceivedProps = AudioAttachmentSharedProps\n\nconst AudioComposer: React.FC<AudioComposerProps> = (props) => (\n <AudioAttachmentRow {...props} state=\"composer\" />\n)\nconst AudioSent: React.FC<AudioSentProps> = (props) => (\n <AudioAttachmentRow {...props} state=\"sent\" />\n)\nconst AudioReceived: React.FC<AudioReceivedProps> = (props) => (\n <AudioAttachmentRow {...props} state=\"received\" />\n)\n\nconst AudioAttachment = {\n Composer: AudioComposer,\n Sent: AudioSent,\n Received: AudioReceived,\n}\n\nexport default AudioAttachment\n","import {\n getDocumentIconType,\n getSourceType,\n} from '../../AttachmentCard/utils/mimeType'\n\n/**\n * Format a byte count as a short human-readable string (`'379.5 KB'`).\n * Mirrors the meta line shown next to file attachments in the mobile chat\n * design — `EXT · SIZE`, e.g. `PDF · 379.5 KB`.\n */\nexport function formatFileSize(bytes: number): string {\n if (!Number.isFinite(bytes) || bytes < 0) return ''\n if (bytes < 1024) return `${bytes} B`\n if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`\n if (bytes < 1024 * 1024 * 1024)\n return `${(bytes / (1024 * 1024)).toFixed(2)} MB`\n return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`\n}\n\nconst EXTENSION_LABEL_BY_DOCUMENT_TYPE: Record<string, string> = {\n pdf: 'PDF',\n doc: 'DOC',\n xls: 'XLS',\n csv: 'CSV',\n ppt: 'PPT',\n zip: 'ZIP',\n text: 'TXT',\n markdown: 'MD',\n}\n\n/**\n * Returns the short uppercase extension label shown in the meta line of\n * compact document / file attachments. Prefers the filename extension\n * when present (matches the mobile design which trusts the filename),\n * and falls back to a label derived from the MIME type.\n */\nexport function getFileExtensionLabel(\n mimeType?: string,\n filename?: string\n): string | undefined {\n if (filename) {\n const lastDot = filename.lastIndexOf('.')\n if (lastDot > 0 && lastDot < filename.length - 1) {\n const ext = filename.slice(lastDot + 1)\n if (ext && ext.length <= 5) return ext.toUpperCase()\n }\n }\n\n if (!mimeType) return undefined\n\n const sourceType = getSourceType(mimeType)\n if (sourceType === 'document') {\n const docType = getDocumentIconType(mimeType)\n const docLabel = EXTENSION_LABEL_BY_DOCUMENT_TYPE[docType]\n if (docLabel) return docLabel\n if (mimeType === 'application/octet-stream') return undefined\n }\n\n const subtype = mimeType.split('/')[1]\n if (!subtype || subtype === '*') return undefined\n return subtype.toUpperCase()\n}\n\n/**\n * Build the meta line shown under the title in compact document / file\n * attachments — `EXT · SIZE` (`PDF · 379.5 KB`). Either part is dropped\n * when not available so audio / generic files still get a useful label.\n */\nexport function buildCompactMetaLabel(\n mimeType?: string,\n filename?: string,\n fileSize?: number\n): string | undefined {\n const ext = getFileExtensionLabel(mimeType, filename)\n const size =\n typeof fileSize === 'number' && fileSize > 0\n ? formatFileSize(fileSize)\n : undefined\n return [ext, size].filter(Boolean).join(' · ') || undefined\n}\n\n/**\n * Derive a sensible filename from a URL — used as the fallback save name\n * when the attachment has no `filename` set. Returns `'download'` for\n * URLs we can't parse.\n */\nexport function filenameFromUrl(url: string): string {\n try {\n const parsed = new URL(url)\n const last = parsed.pathname.split('/').pop()\n return last && last.length > 0 ? decodeURIComponent(last) : 'download'\n } catch {\n return 'download'\n }\n}\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport { renderTypeIcon } from '../../AttachmentCard'\nimport type { BubbleVariant } from '../types'\n\nimport { buildCompactMetaLabel } from './fileMeta'\n\nexport interface CompactDocumentRowProps {\n variant: BubbleVariant\n /** Filename — drives the title (when no `title` set) and the `EXT` label. */\n filename?: string\n /** Override displayed title. Defaults to `filename`. */\n title?: string\n mimeType?: string\n fileSize?: number\n /**\n * When set, the icon + filename area becomes a `<button>` that fires\n * this handler. Lets the row's primary activation (e.g. opening the\n * PDF viewer) live separately from the trailing slot's own button\n * (e.g. a download icon) without nesting `<button>` inside `<button>`.\n * When omitted, the row renders as a plain presentational `<div>`\n * and the caller is responsible for any outer activation wrapper.\n */\n onActivate?: () => void\n /**\n * Required when `onActivate` is set. Visible to assistive tech as\n * the inner button's label (e.g. `'Open ESOP-summary.pdf'`).\n */\n activateLabel?: string\n /**\n * Optional trailing slot — sits outside the activation button so it\n * can host its own interactive controls (e.g. a download `<button>`,\n * the Composer's `DismissButton`) without nesting one button inside\n * another.\n */\n trailingAction?: React.ReactNode\n}\n\n// Meta line + icon tile are tinted relative to the bubble background,\n// so they stay variant-aware. The title inherits the bubble's text\n// color (set on the parent `Bubble`).\n//\n// `text-black/55` on the `#f2f1ef` light bubble lands right at the\n// WCAG AA 4.5:1 small-text threshold (≈4.5:1) — bump to `/65` so\n// the meta line has a comfortable margin on the light bubble. The\n// dark variant (`text-white/55` on `#1e2330`) already passes AA,\n// so we leave it alone.\nconst META_CLASS_BY_VARIANT: Record<BubbleVariant, string> = {\n dark: 'text-white/55',\n light: 'text-black/65',\n}\n\nconst ICON_BG_BY_VARIANT: Record<BubbleVariant, string> = {\n dark: 'bg-white/10',\n light: 'bg-black/5',\n}\n\nconst ICON_COLOR_BY_VARIANT: Record<BubbleVariant, string> = {\n dark: 'text-white/85',\n light: 'text-black/85',\n}\n\n/**\n * Compact icon-row used by `Pdf` / `File` `MessageAttachment` variants.\n * Mirrors the mobile chat document attachment from\n * `EARN-8448/pr-2-chat-bubbles-and-file-icons` — a 40x40 typed icon\n * tile on the left, filename + `EXT · SIZE` meta on the right.\n *\n * The row is always wrapped by `Bubble`, which contributes the bubble\n * background, padding, border, and (when the parent wires it up) the\n * click target.\n */\nconst CompactDocumentRow: React.FC<CompactDocumentRowProps> = ({\n variant,\n filename,\n title,\n mimeType = 'application/octet-stream',\n fileSize,\n onActivate,\n activateLabel,\n trailingAction,\n}) => {\n const resolvedTitle = title ?? filename ?? 'File'\n const metaLabel = buildCompactMetaLabel(mimeType, filename, fileSize)\n\n const iconTile = (\n <div\n className={classNames(\n 'flex size-10 shrink-0 items-center justify-center rounded-sm',\n ICON_BG_BY_VARIANT[variant]\n )}\n aria-hidden\n >\n {renderTypeIcon(mimeType, {\n className: classNames('size-6', ICON_COLOR_BY_VARIANT[variant]),\n weight: 'regular',\n })}\n </div>\n )\n\n const textBlock = (\n <div className=\"flex min-w-0 flex-1 flex-col text-left\">\n <p className=\"truncate font-medium leading-snug\">{resolvedTitle}</p>\n {metaLabel ? (\n <p\n className={classNames(\n 'truncate text-xs leading-4',\n META_CLASS_BY_VARIANT[variant]\n )}\n >\n {metaLabel}\n </p>\n ) : null}\n </div>\n )\n\n // When the row is activatable, its body (icon + text) becomes the\n // click target — wrapping it in a `<button>` rather than wrapping\n // the entire row preserves the trailing slot for its own buttons\n // (download / dismiss) instead of nesting them inside one.\n const body = onActivate ? (\n <button\n type=\"button\"\n onClick={onActivate}\n aria-label={activateLabel}\n className={classNames(\n 'flex min-w-0 flex-1 items-center gap-3 rounded-sm text-left transition-colors',\n variant === 'dark'\n ? 'hover:bg-white/[0.04]'\n : 'hover:bg-black/[0.04]'\n )}\n >\n {iconTile}\n {textBlock}\n </button>\n ) : (\n <>\n {iconTile}\n {textBlock}\n </>\n )\n\n return (\n <div className=\"flex items-center gap-3 px-3 py-2\">\n {body}\n {trailingAction ? <div className=\"shrink-0\">{trailingAction}</div> : null}\n </div>\n )\n}\n\nexport default CompactDocumentRow\n","import { filenameFromUrl } from './fileMeta'\n\n/**\n * Download a remote asset with the desired filename.\n *\n * Tries the same-origin `fetch` → `Blob` → invisible-`<a>.download`\n * route first so the file lands on disk under `filename` instead of\n * navigating the tab to it. When CORS / network errors trip us up we\n * fall back to `window.open` and then to a no-window anchor — the\n * popup-blocker fallback ensures the download still fires even when\n * a browser blocks the `_blank` window.\n *\n * Shared by `DownloadAction` (rendered as an explicit Download button\n * in PDF / image / video viewers) and `MessageAttachment.File` rows\n * (where the entire row is the download trigger). Centralizing here\n * keeps one source of truth for the fallback chain.\n */\nexport async function triggerDownload(\n url: string,\n filename?: string\n): Promise<void> {\n const name = filename ?? filenameFromUrl(url)\n\n try {\n const res = await fetch(url, { mode: 'cors' })\n if (!res.ok) throw new Error(`HTTP ${res.status}`)\n const blob = await res.blob()\n const objectUrl = URL.createObjectURL(blob)\n const a = document.createElement('a')\n a.href = objectUrl\n a.download = name\n a.style.display = 'none'\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n URL.revokeObjectURL(objectUrl)\n } catch {\n const fallback = window.open(url, '_blank', 'noopener,noreferrer')\n if (!fallback) {\n // popup blocked — fall back to a non-windowed anchor\n const a = document.createElement('a')\n a.href = url\n a.download = name\n a.target = '_blank'\n a.rel = 'noopener noreferrer'\n a.style.display = 'none'\n document.body.appendChild(a)\n a.click()\n document.body.removeChild(a)\n }\n }\n}\n\nexport default triggerDownload\n","import { DownloadSimpleIcon } from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React from 'react'\n\nimport Bubble from '../_shared/Bubble'\nimport CompactDocumentRow from '../_shared/CompactDocumentRow'\nimport DismissButton from '../_shared/DismissButton'\nimport { filenameFromUrl } from '../_shared/fileMeta'\nimport { triggerDownload } from '../_shared/triggerDownload'\nimport {\n bubbleVariantForState,\n type BubbleVariant,\n type ComposerExtras,\n type FileItem,\n type MessageAttachmentBaseProps,\n type MessageAttachmentState,\n} from '../types'\n\nexport interface FileAttachmentSharedProps extends MessageAttachmentBaseProps {\n /** Source URL of the file (used as the download target). */\n src?: string\n /** Filename — drives the title + the meta line + the download default name. */\n filename?: string\n fileSize?: number\n /** MIME type — drives the type icon. Defaults to `application/octet-stream`. */\n mimeType?: string\n /**\n * Override displayed title (defaults to `filename`). Useful when a\n * sender renames the attachment before sending.\n */\n title?: string\n /**\n * Stacked files. Takes precedence over `src` when set. Each item\n * renders as its own row inside the bubble; clicking a row downloads\n * that file. Sent + Received only — the composer surface accepts a\n * single attachment at a time.\n */\n items?: FileItem[]\n /**\n * Forwarded to the row trigger. When omitted the click still\n * downloads the file. Supply this for analytics, or return `false`\n * to fully intercept the download (e.g. show a confirmation modal,\n * route to a custom preview, throttle large files). Any other\n * return value — including `void`/`undefined` — lets the built-in\n * download proceed. For stacked attachments the `index` argument\n * identifies the row.\n */\n onClick?: (index: number) => boolean | void\n}\n\nconst resolveItems = ({\n src,\n filename,\n fileSize,\n mimeType,\n title,\n items,\n}: {\n src?: string\n filename?: string\n fileSize?: number\n mimeType?: string\n title?: string\n items?: FileItem[]\n}): FileItem[] => {\n if (items && items.length > 0) return items\n if (src) return [{ src, filename, fileSize, mimeType, title }]\n return []\n}\n\ninterface FileRowButtonProps {\n variant: BubbleVariant\n item: FileItem\n index: number\n onActivate: (index: number) => void\n trailingAction: React.ReactNode\n}\n\n/**\n * Single tappable file row — used both for the lone file case and\n * for each child of a stacked attachment. The icon + filename area\n * is the click target (downloads the asset); the trailing slot is\n * decorative on Sent / Received and hosts the dismiss control on\n * Composer.\n */\nconst FileRowButton: React.FC<FileRowButtonProps> = ({\n variant,\n item,\n index,\n onActivate,\n trailingAction,\n}) => {\n const resolvedFilename = item.filename ?? filenameFromUrl(item.src)\n return (\n <CompactDocumentRow\n variant={variant}\n filename={resolvedFilename}\n title={item.title}\n mimeType={item.mimeType ?? 'application/octet-stream'}\n fileSize={item.fileSize}\n onActivate={() => onActivate(index)}\n activateLabel={`Download ${resolvedFilename}`}\n trailingAction={trailingAction}\n />\n )\n}\n\ninterface InternalFileRowProps extends FileAttachmentSharedProps {\n state: MessageAttachmentState\n onDismiss?: () => void\n}\n\nconst FileAttachmentRow: React.FC<InternalFileRowProps> = ({\n state,\n src,\n filename,\n fileSize,\n mimeType,\n title,\n items,\n text,\n groupPosition,\n onClick,\n onDismiss,\n}) => {\n const variant = bubbleVariantForState(state)\n const showDismiss = state === 'composer' && !!onDismiss\n const resolvedItems = resolveItems({\n src,\n filename,\n fileSize,\n mimeType,\n title,\n items,\n })\n\n // `useCallback` would be pointless here — `resolvedItems` is rebuilt\n // every render, so memoizing on it produces a fresh function every\n // render anyway. Keep this as a plain function.\n const handleActivate = (index: number) => {\n // `onClick` returning `false` cancels the built-in download so\n // consumers can route the click to a confirmation modal, custom\n // preview, etc. Any other return value (including void) lets the\n // default download proceed.\n if (onClick?.(index) === false) return\n const item = resolvedItems[index]\n if (!item) return\n const resolvedFilename = item.filename ?? filenameFromUrl(item.src)\n void triggerDownload(item.src, resolvedFilename)\n }\n\n if (resolvedItems.length === 0) {\n return null\n }\n\n const downloadIcon = (\n <span\n className={classNames(\n 'flex size-8 items-center justify-center rounded-full',\n variant === 'dark' ? 'text-white/70' : 'text-black/70'\n )}\n aria-hidden\n >\n <DownloadSimpleIcon className=\"size-5\" weight=\"bold\" />\n </span>\n )\n\n return (\n <Bubble\n variant={variant}\n text={text}\n groupPosition={groupPosition}\n data-testid=\"file-attachment\"\n >\n {/* Stacked rows get a small vertical gap so each file reads as\n a discrete attachment rather than one merged blob. Single\n row falls through to a no-op `gap-0`. */}\n <div className=\"flex flex-col gap-2\">\n {resolvedItems.map((item, index) => {\n // Composer only supports a single attachment so the dismiss\n // control sits on the only row; otherwise every row gets\n // the trailing download icon hint.\n const trailingAction =\n showDismiss && index === 0 ? (\n <DismissButton onClick={onDismiss!} variant=\"inline\" />\n ) : (\n downloadIcon\n )\n return (\n <FileRowButton\n key={`${item.src}-${index}`}\n variant={variant}\n item={item}\n index={index}\n onActivate={handleActivate}\n trailingAction={trailingAction}\n />\n )\n })}\n </div>\n </Bubble>\n )\n}\n\n/**\n * Composer-only props. Single file (`src`) only — the composer surface\n * accepts a single attachment at a time, so `items` is not supported.\n * Captions (`text`) and `groupPosition` are also dropped: the composer\n * renders a standalone draft, not part of a same-author run, and\n * captions live in the message-input textarea, not inside the draft\n * attachment preview.\n */\nexport interface FileComposerProps extends ComposerExtras {\n src?: string\n filename?: string\n fileSize?: number\n mimeType?: string\n title?: string\n onClick?: (index: number) => boolean | void\n}\nexport type FileSentProps = FileAttachmentSharedProps\nexport type FileReceivedProps = FileAttachmentSharedProps\n\nconst FileComposer: React.FC<FileComposerProps> = (props) => (\n <FileAttachmentRow {...props} state=\"composer\" />\n)\nconst FileSent: React.FC<FileSentProps> = (props) => (\n <FileAttachmentRow {...props} state=\"sent\" />\n)\nconst FileReceived: React.FC<FileReceivedProps> = (props) => (\n <FileAttachmentRow {...props} state=\"received\" />\n)\n\nconst FileAttachment = {\n Composer: FileComposer,\n Sent: FileSent,\n Received: FileReceived,\n}\n\nexport default FileAttachment\n","import { CaretLeftIcon, CaretRightIcon } from '@phosphor-icons/react'\nimport React from 'react'\n\nexport interface CarouselNavProps {\n onPrev: () => void\n onNext: () => void\n /** Accessible label for the previous-item button. */\n prevLabel?: string\n /** Accessible label for the next-item button. */\n nextLabel?: string\n}\n\n/**\n * Prev / next chrome shared by every carousel-aware viewer. Rendered\n * inside the dialog body so the buttons sit absolutely positioned\n * against the viewer (`.mes-media-viewer__nav--{prev,next}`).\n *\n * Styling lives as plain CSS in `styles.css` so the buttons render\n * correctly regardless of whether the consumer has Tailwind wired up.\n */\nconst CarouselNav: React.FC<CarouselNavProps> = ({\n onPrev,\n onNext,\n prevLabel = 'Previous',\n nextLabel = 'Next',\n}) => (\n <>\n <button\n type=\"button\"\n onClick={onPrev}\n aria-label={prevLabel}\n className=\"mes-media-viewer__nav mes-media-viewer__nav--prev\"\n >\n <CaretLeftIcon size={20} weight=\"bold\" aria-hidden />\n </button>\n <button\n type=\"button\"\n onClick={onNext}\n aria-label={nextLabel}\n className=\"mes-media-viewer__nav mes-media-viewer__nav--next\"\n >\n <CaretRightIcon size={20} weight=\"bold\" aria-hidden />\n </button>\n </>\n)\n\nexport default CarouselNav\n","import {\n CircleNotchIcon,\n DownloadSimpleIcon,\n IconProps,\n} from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React, { useCallback, useState } from 'react'\n\nimport { triggerDownload } from './triggerDownload'\n\nexport type DownloadActionVariant =\n /** Solid pill button used inside compact / file rows. */\n | 'pill'\n /**\n * Compact round icon button sized for the trailing slot of a\n * `CompactDocumentRow` (PDF / File rows). Adopts the row's tone so\n * it sits inline next to the filename without competing with it.\n */\n | 'inline'\n /**\n * Round icon button sized to match the close action in the\n * `ViewerShell` chrome — used by `ImageViewer` for in-viewer\n * downloads. Styled with plain CSS (`.mes-media-viewer__action`) so\n * it renders correctly without a Tailwind dependency in the\n * consumer's app.\n */\n | 'viewer'\n\nexport interface DownloadActionProps {\n url: string\n filename?: string\n variant?: DownloadActionVariant\n /**\n * Override the visible label on `pill` variants. Defaults to\n * `'Download'`. On `inline` / `viewer` variants the label is\n * hidden visually and surfaced as the button's `aria-label`.\n */\n label?: string\n /** Hide the label, keeping just the icon. Defaults to `true` for non-pill variants. */\n iconOnly?: boolean\n /** Tone of the surface. Used by the `pill` variant. */\n tone?: 'dark' | 'light'\n /**\n * Triggered after the download starts so consumers can fire analytics\n * or close a viewer. Errors during download don't suppress the call.\n */\n onTriggered?: () => void\n}\n\nconst DownloadAction: React.FC<DownloadActionProps> = ({\n url,\n filename,\n variant = 'pill',\n label = 'Download',\n iconOnly,\n tone = 'dark',\n onTriggered,\n}) => {\n const [busy, setBusy] = useState(false)\n\n const handleClick = useCallback(\n (e: React.MouseEvent) => {\n e.stopPropagation()\n if (busy) return\n setBusy(true)\n triggerDownload(url, filename)\n .catch(() => {\n /* swallowed — fallback path inside `triggerDownload` */\n })\n .finally(() => {\n setBusy(false)\n onTriggered?.()\n })\n },\n [busy, url, filename, onTriggered]\n )\n\n const showIconOnly = iconOnly ?? variant !== 'pill'\n\n const iconClass = classNames(\n variant === 'pill' ? 'size-4' : 'size-5',\n 'shrink-0'\n )\n const iconProps: IconProps = { className: iconClass, weight: 'bold' }\n\n if (variant === 'inline') {\n // Sized to match the existing trailing slot used by `DismissButton`\n // and the decorative download span in `FileAttachment`. Tone keys\n // off the surrounding `Bubble` variant so a sender (dark) bubble\n // gets a lighter icon and a receiver (light) bubble gets a darker\n // one — same approach `CompactDocumentRow` already uses for the\n // type icon.\n const inlineToneClasses: Record<'dark' | 'light', string> = {\n dark: 'text-white/70 hover:bg-white/[0.08] hover:text-white',\n light: 'text-black/70 hover:bg-black/[0.08] hover:text-black',\n }\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={busy}\n aria-label={label}\n className={classNames(\n 'flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:opacity-70',\n inlineToneClasses[tone]\n )}\n >\n {busy ? (\n <CircleNotchIcon\n className=\"size-4 animate-spin\"\n weight=\"bold\"\n aria-hidden\n />\n ) : (\n <DownloadSimpleIcon\n className=\"size-5 shrink-0\"\n weight=\"bold\"\n aria-hidden\n />\n )}\n </button>\n )\n }\n\n if (variant === 'viewer') {\n // Styled entirely through `.mes-media-viewer__action` (plain CSS\n // in `styles.css`) so the button renders correctly when consumed\n // outside this repo without a Tailwind setup. Sits next to the\n // close action inside `ViewerShell`'s top-right chrome.\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={busy}\n aria-label={label}\n className=\"mes-media-viewer__action\"\n >\n {busy ? (\n <CircleNotchIcon size={20} weight=\"bold\" aria-hidden />\n ) : (\n <DownloadSimpleIcon size={20} weight=\"bold\" aria-hidden />\n )}\n </button>\n )\n }\n\n // pill — only remaining variant\n return (\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={busy}\n aria-label={showIconOnly ? label : undefined}\n className={classNames(\n 'mt-3 inline-flex h-10 w-full items-center justify-center gap-2 rounded-full px-4 text-sm font-medium leading-none transition-colors disabled:opacity-70',\n tone === 'dark'\n ? 'bg-[#121110] text-white hover:bg-[#2a2928]'\n : 'bg-white text-[#121110] hover:bg-white/90'\n )}\n >\n {busy ? (\n <CircleNotchIcon\n className=\"size-4 animate-spin\"\n weight=\"bold\"\n aria-hidden\n />\n ) : (\n <DownloadSimpleIcon {...iconProps} aria-hidden />\n )}\n {showIconOnly ? null : label}\n </button>\n )\n}\n\nexport default DownloadAction\n","/* eslint-disable react-hooks/set-state-in-effect -- pre-existing pattern flagged by the React\n Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is\n out of scope for the lint toolchain upgrade. */\nimport { useCallback, useEffect, useState } from 'react'\n\nconst clamp = (value: number, min: number, max: number) =>\n Math.min(Math.max(value, min), max)\n\nexport interface UseCarouselOptions {\n /** Total number of items in the carousel. */\n length: number\n /** Index to display when the carousel first opens / re-opens. */\n initialIndex: number\n /** When `false`, the keyboard listener is detached. */\n open: boolean\n}\n\nexport interface UseCarouselResult {\n /** Currently displayed index (clamped to `[0, length - 1]`). */\n index: number\n /** Step one item back, wrapping at the start. */\n prev: () => void\n /** Step one item forward, wrapping at the end. */\n next: () => void\n}\n\n/**\n * Tiny carousel state used by `ImageViewer`, `VideoViewer`, and\n * `PdfViewer` when their items array has more than one entry.\n *\n * - Resets to `initialIndex` whenever the viewer (re-)opens, so\n * clicking a different tile on the bubble surface lands on that\n * item rather than restoring the last-viewed position.\n * - Clamps defensively when `length` shrinks below the active index\n * (server push / optimistic update / stack edit) so the viewer\n * gracefully falls back instead of dereferencing `undefined`.\n * - Listens for `ArrowLeft` / `ArrowRight` while the viewer is open,\n * but yields when the focused element is a `<video>` / `<audio>`\n * element so the native media controls can use the same keys for\n * seek / volume.\n */\nexport const useCarousel = ({\n length,\n initialIndex,\n open,\n}: UseCarouselOptions): UseCarouselResult => {\n const safeInitial = clamp(initialIndex, 0, Math.max(length - 1, 0))\n const [index, setIndex] = useState(safeInitial)\n\n // Snap the active index back to whatever the caller asked for every\n // time the viewer (re)opens or its initial target / length changes.\n // Without this the viewer would remember the last index across\n // open/close cycles, which is wrong for a chat lightbox where each\n // tile click should land on that tile.\n useEffect(() => {\n if (!open) return\n setIndex(clamp(initialIndex, 0, Math.max(length - 1, 0)))\n }, [open, initialIndex, length])\n\n // Re-clamp on length change so a stale index doesn't dereference\n // past the new array bounds (e.g. items shrank while the viewer is\n // open). The `Math.max(..., 0)` keeps the index at `0` when the\n // array empties out so callers can render an empty-state instead of\n // a runtime crash.\n useEffect(() => {\n setIndex((i) => clamp(i, 0, Math.max(length - 1, 0)))\n }, [length])\n\n const prev = useCallback(() => {\n if (length <= 1) return\n setIndex((i) => (i <= 0 ? length - 1 : i - 1))\n }, [length])\n\n const next = useCallback(() => {\n if (length <= 1) return\n setIndex((i) => (i >= length - 1 ? 0 : i + 1))\n }, [length])\n\n useEffect(() => {\n if (!open || length <= 1) return undefined\n\n const onKey = (e: KeyboardEvent) => {\n if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return\n // Don't hijack arrow keys when a native media element is\n // focused — the browser's own controls already use them for\n // seek / volume, and stealing them would feel broken.\n const active = document.activeElement\n if (\n active &&\n (active.tagName === 'VIDEO' || active.tagName === 'AUDIO')\n ) {\n return\n }\n e.preventDefault()\n if (e.key === 'ArrowLeft') prev()\n else next()\n }\n\n window.addEventListener('keydown', onKey)\n return () => window.removeEventListener('keydown', onKey)\n }, [open, length, prev, next])\n\n return { index, prev, next }\n}\n\nexport default useCarousel\n","import { XIcon } from '@phosphor-icons/react'\nimport React, { useEffect, useRef } from 'react'\n\nexport interface ViewerShellProps {\n open: boolean\n onClose: () => void\n /**\n * Accessible label for the dialog — usually the media's filename. We\n * intentionally don't render a visible title bar (the chrome is a\n * single close button + optional actions so the media stays the\n * focus), so this is the only way assistive tech gets a name for\n * the modal.\n */\n ariaLabel?: string\n /**\n * Optional counter rendered in the top-left chrome (e.g. `2 / 6`).\n * Carousel-aware viewers set this when their items array has more\n * than one entry; omit it for single-item viewers.\n */\n counter?: React.ReactNode\n /**\n * Extra buttons rendered alongside the close button in the top-right\n * chrome (e.g. a download action). They share `.mes-media-viewer__action`\n * styling so the row reads as one consistent button group.\n */\n actions?: React.ReactNode\n children: React.ReactNode\n 'data-testid'?: string\n}\n\n/**\n * Full-viewport lightbox shell shared by `ImageViewer`, `VideoViewer`,\n * and `PdfViewer`. Built on a native `<dialog>` so the package ships\n * without a Tailwind dependency for the load-bearing chrome:\n *\n * - Top-layer + body-scroll lock come for free from `showModal()`.\n * - `Escape` to close is wired by the platform.\n * - Focus management (move-in on open, restore on close) is handled\n * by the browser; we explicitly focus the close button as a\n * fallback for environments where `showModal` is unavailable\n * (jsdom under tests).\n *\n * The structural styling lives as plain CSS under `.mes-media-viewer*`\n * in `styles.css` so a consumer who imports\n * `@linktr.ee/messaging-react/styles.css` gets a working lightbox even\n * if their Tailwind pipeline isn't scanning our `dist`.\n */\nconst ViewerShell: React.FC<ViewerShellProps> = ({\n open,\n onClose,\n ariaLabel,\n counter,\n actions,\n children,\n 'data-testid': dataTestId,\n}) => {\n const dialogRef = useRef<HTMLDialogElement | null>(null)\n const closeButtonRef = useRef<HTMLButtonElement | null>(null)\n\n // Drive `<dialog>` open/close imperatively from the React `open` prop.\n // We guard `showModal` / `close` because jsdom 23 ships the element\n // without those methods — falling back to the `[open]` attribute\n // keeps tests (and any other non-DOM environment) functional even if\n // the true top-layer modal behavior isn't available there.\n useEffect(() => {\n const dialog = dialogRef.current\n if (!dialog) return undefined\n\n if (open) {\n if (!dialog.open) {\n if (typeof dialog.showModal === 'function') {\n try {\n dialog.showModal()\n } catch {\n dialog.setAttribute('open', '')\n }\n } else {\n dialog.setAttribute('open', '')\n }\n }\n\n const previouslyFocused =\n typeof document !== 'undefined'\n ? (document.activeElement as HTMLElement | null)\n : null\n\n // Explicitly focus the close button. In a real browser\n // `showModal()` would already focus the first sequentially\n // focusable child (which is the close button), but doing it\n // ourselves guarantees the same behavior in jsdom and in any\n // browser where focus heuristics differ.\n closeButtonRef.current?.focus()\n\n return () => {\n if (dialog.open) {\n if (typeof dialog.close === 'function') {\n try {\n dialog.close()\n } catch {\n dialog.removeAttribute('open')\n }\n } else {\n dialog.removeAttribute('open')\n }\n }\n if (previouslyFocused && document.body.contains(previouslyFocused)) {\n previouslyFocused.focus()\n }\n }\n }\n\n if (dialog.open) {\n if (typeof dialog.close === 'function') {\n try {\n dialog.close()\n } catch {\n dialog.removeAttribute('open')\n }\n } else {\n dialog.removeAttribute('open')\n }\n }\n return undefined\n }, [open])\n\n // The platform fires `close` when the user dismisses via Escape or\n // any other native path. Mirror that into React state so the parent's\n // `open` flag stays in sync — otherwise the dialog would close\n // visually but our effect would re-open it on the next render.\n const handleDialogClose = () => {\n onClose()\n }\n\n // Backdrop click closes the viewer. The click lands on the `<dialog>`\n // itself (not its children) because the body sits inside an inner\n // wrapper — the easiest reliable check.\n const handleDialogClick = (e: React.MouseEvent<HTMLDialogElement>) => {\n if (e.target === dialogRef.current) onClose()\n }\n\n return (\n // The native `<dialog>` element ships its own keyboard dismissal\n // (Escape, handled by the platform and surfaced through `onClose`),\n // so the `onClick` on the backdrop doesn't need a paired keyboard\n // listener — that's the whole point of using `<dialog>`. The\n // `jsx-a11y` rules below don't model `<dialog>` as interactive, so\n // we silence them locally with the rationale captured here.\n // eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-noninteractive-element-interactions\n <dialog\n ref={dialogRef}\n className=\"mes-media-viewer\"\n aria-label={ariaLabel ?? 'Attachment viewer'}\n data-testid={dataTestId}\n onClose={handleDialogClose}\n onClick={handleDialogClick}\n >\n <div className=\"mes-media-viewer__chrome\">\n {counter ? (\n <span className=\"mes-media-viewer__counter\">{counter}</span>\n ) : null}\n <div className=\"mes-media-viewer__actions\">\n {actions}\n <button\n ref={closeButtonRef}\n type=\"button\"\n onClick={onClose}\n aria-label=\"Close viewer\"\n className=\"mes-media-viewer__action\"\n >\n <XIcon size={20} weight=\"bold\" aria-hidden />\n </button>\n </div>\n </div>\n <div className=\"mes-media-viewer__body\">{children}</div>\n </dialog>\n )\n}\n\nexport default ViewerShell\n","import React, { useMemo } from 'react'\n\nimport CarouselNav from './CarouselNav'\nimport DownloadAction from './DownloadAction'\nimport { filenameFromUrl } from './fileMeta'\nimport { useCarousel } from './useCarousel'\nimport ViewerShell from './ViewerShell'\n\nexport interface ImageViewerItem {\n src: string\n alt?: string\n /**\n * Filename used by the download action and as the dialog's\n * accessible name. Falls back to the last pathname segment of\n * `src` when omitted.\n */\n filename?: string\n}\n\nexport interface ImageViewerProps {\n open: boolean\n items: ImageViewerItem[]\n /** Index to display when the viewer first opens. Defaults to `0`. */\n initialIndex?: number\n onClose: () => void\n}\n\n/**\n * Native lightbox-style image viewer — full-viewport `<dialog>` with\n * the image centered at reasonable max dimensions, a single close\n * button, and a download action in the chrome.\n *\n * When `items.length > 1` the viewer becomes a carousel:\n *\n * - Prev / next on-screen controls hug the viewport sides.\n * - `ArrowLeft` / `ArrowRight` step between siblings (the keyboard\n * listener is parked while a `<video>` / `<audio>` element is\n * focused so native media seek still works in mixed contexts).\n * - A `current / total` counter sits in the top-left chrome.\n *\n * Used by every `MessageAttachment.Image.*` variant\n * (Composer / Sent / Received) so admins can preview attachments\n * before sending and recipients can preview after receipt.\n */\nconst ImageViewer: React.FC<ImageViewerProps> = ({\n open,\n items,\n initialIndex = 0,\n onClose,\n}) => {\n const { index, prev, next } = useCarousel({\n length: items.length,\n initialIndex,\n open,\n })\n\n const item = items[index]\n const filename = useMemo(\n () => item?.filename ?? (item ? filenameFromUrl(item.src) : 'image'),\n [item]\n )\n\n if (!item) return null\n\n return (\n <ViewerShell\n open={open}\n onClose={onClose}\n ariaLabel={filename}\n counter={\n items.length > 1 ? `${index + 1} / ${items.length}` : undefined\n }\n actions={\n <DownloadAction\n url={item.src}\n filename={filename}\n variant=\"viewer\"\n label={`Download ${filename}`}\n />\n }\n data-testid=\"image-viewer\"\n >\n {open ? (\n <img\n // Forcing a key swap on item change ensures React replaces\n // the `<img>` cleanly between siblings — otherwise the\n // previous image stays painted for a frame while the new\n // `src` decodes, which reads as a stutter at carousel pace.\n key={`${index}:${item.src}`}\n src={item.src}\n alt={item.alt ?? filename}\n draggable={false}\n // The user has explicitly opened the viewer, so we want the\n // active image to appear immediately rather than fall to the\n // browser's lazy-load heuristics. Which is exactly why the\n // element is gated on `open`: `ViewerShell` keeps its\n // `<dialog>` mounted across open/close so the platform\n // transition can play, and an eager `<img>` inside a closed\n // dialog made every image bubble in a thread download its\n // full-resolution original — note the viewer intentionally\n // uses the raw `item.src`, not the CDN-optimised URL the\n // bubble tile renders.\n loading=\"eager\"\n decoding=\"async\"\n className=\"mes-media-viewer__image\"\n />\n ) : null}\n\n {items.length > 1 ? (\n <CarouselNav\n onPrev={prev}\n onNext={next}\n prevLabel=\"Previous image\"\n nextLabel=\"Next image\"\n />\n ) : null}\n </ViewerShell>\n )\n}\n\nexport default ImageViewer\n","import classNames from 'classnames'\nimport React from 'react'\n\nexport interface MediaStackTile {\n /** Renderable tile content (image / video / poster). */\n content: React.ReactNode\n /** Pure-tile aria label (e.g. `'Photo 1'`). Used by `Pressable` mode. */\n ariaLabel?: string\n}\n\nexport interface MediaStackGridProps {\n tiles: MediaStackTile[]\n /**\n * When set, every tile is wrapped in a `<button>` and forwards the\n * tile index to `onTileActivate`. Used by Image / Video so any tile\n * opens the lightbox / video viewer at the right index.\n */\n onTileActivate?: (index: number) => void\n /**\n * Maximum tiles to render before the last one collapses into an\n * \"+N more\" overflow indicator. Defaults to `4`.\n */\n maxVisible?: number\n /**\n * Aspect ratio (width / height) for the SINGLE-tile layout. Applies\n * to every media kind, not just images. When omitted the 1-tile\n * branch keeps today's `aspect-square`. Ignored for 2+ tiles, whose\n * grid aspect is fixed.\n */\n singleAspectRatio?: number\n className?: string\n}\n\n// The tile is the focusable element, and its focus ring has to be drawn\n// INSIDE it. Figma leaves only a 2px frame around the media and `Bubble`\n// clips its own overflow, so an outset ring is swallowed by the frame;\n// an inset ring or a negative-offset `outline` on the tile is covered by\n// the media, which is `absolute inset-0`. A positioned `::after` is the\n// last child in tree order, so it paints above the media, inherits the\n// tile's outer-corner radius, and ignores every ancestor clip. Same\n// two-tone treatment as before — a dark band over a white ring — so it\n// stays legible on light and dark photos alike. Verified in Chromium.\nconst TILE_SHELL =\n 'relative block size-full overflow-hidden bg-black/5 outline-none after:pointer-events-none after:absolute after:inset-0 after:rounded-[inherit] focus-visible:after:ring-2 focus-visible:after:ring-inset focus-visible:after:ring-white/80 focus-visible:after:ring-offset-2 focus-visible:after:ring-offset-black'\n\n// Figma rounds only the four OUTER corners of a media cluster and leaves\n// every inner edge square: on `attachment-single-image` 1973:13863 the\n// `left` column carries `rectangleCornerRadii [18,0,0,18]` and `right`\n// `[0,18,18,0]`, while the tiles themselves carry none.\n//\n// So the radius lives on the tiles, keyed by layout and DOM position.\n// A single `overflow-hidden rounded-[18px]` container would be shorter,\n// but it also clips the tiles' `focus-visible` ring — an outset\n// `box-shadow` — and in the 1-tile layout the tile fills the container\n// exactly, so keyboard users would get no focus indicator at all.\n// Neither `ring-inset` nor a negative `outline-offset` rescues that:\n// the tile's `<img>` is `absolute inset-0` and paints over both.\n// Measured in Chromium, not assumed.\nconst OUTER_CORNER_CLASSES: Record<number, readonly string[]> = {\n 1: ['rounded-[18px]'],\n 2: ['rounded-l-[18px]', 'rounded-r-[18px]'],\n 3: ['rounded-l-[18px]', 'rounded-tr-[18px]', 'rounded-br-[18px]'],\n 4: [\n 'rounded-tl-[18px]',\n 'rounded-tr-[18px]',\n 'rounded-bl-[18px]',\n 'rounded-br-[18px]',\n ],\n}\n\n// Figma fits a single media item inside a max 326x436 content box on\n// web and 309x436 on mobile, preserving its aspect ratio, and the card\n// then adopts the resulting box — so `object-fit: cover` performs no\n// crop and nothing is letterboxed.\n//\n// The fit is `width = min(widthCap, heightCap * ratio)`, with\n// `aspect-ratio` supplying the other axis. Only the width cap is\n// breakpoint-dependent, so it travels as a custom property while the\n// ratio-derived term resolves to a concrete pixel value. Together they\n// reproduce all eight measured frames (`2032:22651` / `23000` /\n// `23353` / `23707` plus their mobile counterparts) to within Figma's\n// float rounding — including the 9:16 portrait, which renders 246x436\n// on web *and* mobile precisely because the height cap is NOT\n// breakpoint-dependent: a width-only rule would give mobile 309x547.6.\n//\n// The property name is spelled out again inside `SINGLE_WIDTH_CAPS` on\n// purpose. Tailwind extracts classes by scanning source text, so an\n// interpolated `` `[${SINGLE_WIDTH_CAP_PROPERTY}:309px]` `` would emit\n// no CSS at all and silently drop the width cap. Keep it literal.\nconst SINGLE_WIDTH_CAP_PROPERTY = '--mes-single-width-cap'\nconst SINGLE_WIDTH_CAPS =\n '[--mes-single-width-cap:309px] sm:[--mes-single-width-cap:326px]'\nconst SINGLE_HEIGHT_CAP_PX = 436\n\n/**\n * Adaptive grid used by stacked image / video attachments. Layouts:\n *\n * - 1 tile — full-bleed\n * - 2 tiles — equal-width side-by-side row\n * - 3 tiles — one large left tile + two stacked right tiles\n * - 4 tiles — 2×2 grid\n * - 5+ — 2×2 grid with the bottom-right tile showing \"+N more\"\n *\n * A single tile adopts `singleAspectRatio` when supplied and otherwise\n * stays square; 2 tiles sit in a 16:9 row, 3 in a 4:3 box, and 4 in\n * the 379x286 content box Figma draws for a stacked media card. Only\n * the cluster's four outer corners round — see `OUTER_CORNER_CLASSES`.\n */\nconst MediaStackGrid: React.FC<MediaStackGridProps> = ({\n tiles,\n onTileActivate,\n maxVisible = 4,\n singleAspectRatio,\n className,\n}) => {\n const total = tiles.length\n if (total === 0) return null\n\n const visible = tiles.slice(0, Math.min(total, maxVisible))\n const overflow = total - visible.length\n\n const renderTile = (tile: MediaStackTile, index: number, extra?: React.ReactNode) => {\n const sharedClass = classNames(\n TILE_SHELL,\n 'h-full w-full',\n OUTER_CORNER_CLASSES[visible.length]?.[index]\n )\n if (onTileActivate) {\n return (\n <button\n type=\"button\"\n key={index}\n onClick={() => onTileActivate(index)}\n aria-label={tile.ariaLabel ?? `Open media ${index + 1}`}\n className={classNames(sharedClass, 'cursor-zoom-in')}\n >\n {tile.content}\n {extra}\n </button>\n )\n }\n return (\n <div key={index} className={sharedClass}>\n {tile.content}\n {extra}\n </div>\n )\n }\n\n let singleTileStyle: React.CSSProperties | undefined\n if (singleAspectRatio) {\n singleTileStyle = {\n aspectRatio: singleAspectRatio,\n // Hundredths keep the emitted declaration readable in devtools;\n // the rounding error is well under a device pixel.\n width: `min(var(${SINGLE_WIDTH_CAP_PROPERTY}), ${\n Math.round(SINGLE_HEIGHT_CAP_PX * singleAspectRatio * 100) / 100\n }px)`,\n // Only bites in a container narrower than the width cap.\n maxWidth: '100%',\n }\n }\n\n if (visible.length === 1) {\n return (\n <div\n className={classNames(\n singleAspectRatio ? SINGLE_WIDTH_CAPS : 'aspect-square w-full',\n className\n )}\n style={singleTileStyle}\n >\n {renderTile(visible[0], 0)}\n </div>\n )\n }\n\n if (visible.length === 2) {\n return (\n <div\n className={classNames(\n 'grid aspect-[16/9] w-full grid-cols-2 gap-0.5',\n className\n )}\n >\n {visible.map((tile, index) => renderTile(tile, index))}\n </div>\n )\n }\n\n if (visible.length === 3) {\n return (\n <div\n className={classNames(\n 'grid aspect-[4/3] w-full grid-cols-2 grid-rows-2 gap-0.5',\n className\n )}\n >\n <div className=\"row-span-2\">{renderTile(visible[0], 0)}</div>\n {renderTile(visible[1], 1)}\n {renderTile(visible[2], 2)}\n </div>\n )\n }\n\n // 4 tiles (or 4 visible with overflow on the last)\n return (\n <div\n className={classNames(\n // 379x286 is the content box of Figma's 383x290 stacked-media\n // card (`attachment-single-image` 1973:13863) once its 2px\n // frame is removed. Pinning the aspect on the container — as\n // this branch has always done — makes the card resolve to\n // exactly 383x290, and the 2px gutters then split it into\n // 188.5x142 tiles (Figma draws 188x142; its own 383 does not\n // divide evenly, so half a pixel goes to the columns).\n 'grid aspect-[379/286] w-full grid-cols-2 grid-rows-2 gap-0.5',\n className\n )}\n >\n {visible.map((tile, index) => {\n const isLastWithOverflow = overflow > 0 && index === visible.length - 1\n return renderTile(\n tile,\n index,\n isLastWithOverflow ? (\n <div className=\"absolute inset-0 flex items-center justify-center bg-black/55 text-2xl font-semibold text-white\">\n +{overflow}\n </div>\n ) : null\n )\n })}\n </div>\n )\n}\n\nexport default MediaStackGrid\n","import { useCallback, useState } from 'react'\n\n/**\n * Optional click handler used by every viewer-backed attachment.\n *\n * Return `false` to cancel the default open behavior — useful for\n * route-based viewers or confirmation modals. Any other return value\n * (including `void`/`undefined`) lets the built-in viewer open as\n * normal. The `index` argument identifies the row in stacked\n * attachments and is `0` for the single-attachment shape.\n */\nexport type ViewerClickHandler = (index: number) => boolean | void\n\nexport interface UseViewerResult {\n viewerOpen: boolean\n viewerIndex: number\n /**\n * Open the viewer at the given index. Forwards to the caller's\n * `onClick` first so analytics + intercept logic can fire before\n * the viewer mounts; if `onClick` returns `false` the open is\n * skipped entirely.\n */\n handleActivate: (index: number) => void\n closeViewer: () => void\n}\n\n/**\n * Tiny piece of state shared by every `MessageAttachment` that opens\n * a fullscreen viewer (`Image`, `Video`, `Pdf`). Tracks the open flag\n * + the active index, and threads the caller's `onClick` callback\n * through `handleActivate` so consumers can intercept the open\n * (e.g. analytics) or cancel it (return `false` to swap in a\n * route-based viewer / confirmation modal).\n */\nexport const useViewer = (onClick?: ViewerClickHandler): UseViewerResult => {\n const [viewerOpen, setViewerOpen] = useState(false)\n const [viewerIndex, setViewerIndex] = useState(0)\n\n const handleActivate = useCallback(\n (index: number) => {\n if (onClick?.(index) === false) return\n setViewerIndex(index)\n setViewerOpen(true)\n },\n [onClick]\n )\n\n const closeViewer = useCallback(() => setViewerOpen(false), [])\n\n return { viewerOpen, viewerIndex, handleActivate, closeViewer }\n}\n\nexport default useViewer\n","import React from 'react'\n\nimport { optimizeMessagingAttachmentUrl } from '../../../utils/cdnImageUrl'\nimport Bubble from '../_shared/Bubble'\nimport DismissButton from '../_shared/DismissButton'\nimport ImageViewer, {\n type ImageViewerItem,\n} from '../_shared/ImageViewer'\nimport MediaStackGrid, {\n type MediaStackTile,\n} from '../_shared/MediaStackGrid'\nimport { useViewer } from '../_shared/useViewer'\nimport {\n bubbleVariantForState,\n type ImageItem,\n type ImageLoadingMode,\n type MessageAttachmentBaseProps,\n type MessageAttachmentState,\n} from '../types'\n\nexport interface ImageAttachmentSharedProps extends MessageAttachmentBaseProps {\n /** Single image — convenience for the most common case. */\n src?: string\n alt?: string\n /** Filename used as the viewer dialog's accessible name. */\n filename?: string\n /**\n * Stacked images. Takes precedence over `src` when set. Renders a\n * 1 / 2 / 3 / 4-tile grid (5+ collapse into a `+N` overflow tile).\n * Sent + Received only — the composer surface intentionally accepts\n * a single attachment at a time.\n */\n items?: ImageItem[]\n /**\n * Native lazy-load hint forwarded to every `<img>` rendered on the\n * bubble surface (Composer thumbnail, single + stacked tiles, and\n * the `+N` overflow tile). Defaults to `'lazy'` — chat surfaces\n * usually scroll a long history, and lazy loading prevents every\n * historical image from being fetched on mount. Set `'eager'` for\n * above-the-fold hero attachments. Per-tile overrides live on\n * `ImageItem.loading`. The opened `ImageViewer` always eager-loads\n * the active image regardless of this value.\n */\n loading?: ImageLoadingMode\n /**\n * Forwarded to the Image viewer trigger. When omitted the click\n * still opens the built-in viewer — supply this for analytics, or\n * return `false` to intercept the open (e.g. switch to a route-\n * based gallery / confirmation modal). Any other return value\n * (including `void`/`undefined`) lets the built-in viewer open.\n * For stacked attachments the `index` argument identifies the tile.\n */\n onClick?: (index: number) => boolean | void\n}\n\nconst tileFromItem = ({\n item,\n index,\n totalCount,\n fallbackLoading,\n imgRef,\n onLoad,\n}: {\n item: ImageItem\n index: number\n totalCount: number\n fallbackLoading: ImageLoadingMode\n /** Set on the single-image tile only — see `useSingleImageRatio`. */\n imgRef?: React.Ref<HTMLImageElement>\n onLoad?: React.ReactEventHandler<HTMLImageElement>\n}): MediaStackTile => ({\n ariaLabel: `Open image ${index + 1} of ${totalCount}`,\n content: (\n // No corner radius here: `MediaStackGrid` clips the four outer\n // corners of the whole cluster and keeps every inner edge square.\n <img\n ref={imgRef}\n src={optimizeMessagingAttachmentUrl(item.src) ?? item.src}\n alt={item.alt ?? ''}\n width={item.width}\n height={item.height}\n draggable={false}\n loading={item.loading ?? fallbackLoading}\n decoding=\"async\"\n onLoad={onLoad}\n className=\"absolute inset-0 size-full object-cover\"\n />\n ),\n})\n\nconst resolveItems = ({\n src,\n alt,\n items,\n}: {\n src?: string\n alt?: string\n items?: ImageItem[]\n}): ImageItem[] => {\n if (items && items.length > 0) return items\n if (src) return [{ src, alt }]\n return []\n}\n\ninterface InternalImageRowProps extends ImageAttachmentSharedProps {\n state: MessageAttachmentState\n /**\n * Renders a dismiss button on each tile (Composer only). When the\n * stack has multiple tiles, the dismiss applies to the whole stack\n * — that mirrors the mobile composer where attachments are removed\n * as a single unit.\n */\n onDismiss?: () => void\n}\n\n/**\n * Project the bubble's `ImageItem`s onto the carousel-aware viewer's\n * `ImageViewerItem` shape. When a shared outer `filename` is supplied\n * for a stacked bubble, suffix `(N)` per sibling so each carousel page\n * still gets a distinct accessible name + download default.\n */\nconst buildViewerItems = (\n resolvedItems: ImageItem[],\n filename?: string\n): ImageViewerItem[] =>\n resolvedItems.map((item, index) => ({\n src: item.src,\n alt: item.alt,\n filename:\n filename && resolvedItems.length === 1\n ? filename\n : filename\n ? `${filename} (${index + 1})`\n : undefined,\n }))\n\n/**\n * Composer rendering — bare 280px-square image with a dismiss `×`\n * overlay and `rounded-md` corners. Intentionally renders without the\n * shared `Bubble` chrome (no border / no background / no padding) so\n * the in-progress attachment looks like a draft preview, not a sent\n * message. The composer surface only supports a single attachment at\n * a time — `items` and `text` are ignored here.\n */\nconst ImageComposerInner: React.FC<{\n src: string\n alt?: string\n filename?: string\n loading?: ImageLoadingMode\n onClick?: (index: number) => boolean | void\n onDismiss?: () => void\n}> = ({ src, alt, filename, loading = 'lazy', onClick, onDismiss }) => {\n const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(\n onClick\n )\n\n return (\n <div className=\"relative w-fit\">\n <button\n type=\"button\"\n onClick={() => handleActivate(0)}\n aria-label=\"Open image\"\n className=\"block size-[280px] cursor-zoom-in overflow-hidden rounded-md outline-none focus-visible:ring-2 focus-visible:ring-black/40\"\n >\n <img\n src={optimizeMessagingAttachmentUrl(src) ?? src}\n alt={alt ?? ''}\n draggable={false}\n loading={loading}\n decoding=\"async\"\n className=\"size-full object-cover\"\n />\n </button>\n {onDismiss ? (\n <div className=\"absolute right-2 top-2 z-10\">\n <DismissButton onClick={onDismiss} />\n </div>\n ) : null}\n\n <ImageViewer\n open={viewerOpen}\n items={buildViewerItems([{ src, alt }], filename)}\n initialIndex={viewerIndex}\n onClose={closeViewer}\n />\n </div>\n )\n}\n\n/**\n * Box reserved for a single image whose ratio is not known yet — the\n * 4:3 landscape card Figma draws for the most common case (330×250\n * card, 326×246 content, `attachment-single-media` 1972:13149). A\n * bounded reserve keeps a scrolling thread from jumping by an\n * arbitrary amount between mount and decode.\n */\nconst RESERVED_SINGLE_ASPECT_RATIO = 326 / 246\n\n/**\n * Resolves the aspect ratio of a single-image bubble, in order:\n *\n * 1. caller-supplied `ImageItem.width` / `height` — ratio known at\n * first paint, so the card never resizes;\n * 2. the decoded `<img>`'s `naturalWidth` / `naturalHeight`;\n * 3. `RESERVED_SINGLE_ASPECT_RATIO`.\n *\n * Nothing propagates dimensions through the Stream path today, so (2)\n * is the branch that runs in production. It reads both on `load` and\n * from the ref, because an image already in the browser cache can be\n * `complete` before React attaches a `load` listener.\n */\nconst useSingleImageRatio = (item: ImageItem | undefined) => {\n const src = item?.src\n const [measured, setMeasured] = React.useState<{\n src: string\n ratio: number\n }>()\n\n const measure = React.useCallback(\n (img: HTMLImageElement | null) => {\n if (!img || img.naturalWidth <= 0 || img.naturalHeight <= 0) return\n if (src === undefined) return\n const ratio = img.naturalWidth / img.naturalHeight\n // Returning `prev` unchanged matters: the ref callback runs on\n // every commit, and a fresh object each time would schedule an\n // endless render loop.\n setMeasured((prev) =>\n prev && prev.src === src && prev.ratio === ratio ? prev : { src, ratio }\n )\n },\n [src]\n )\n\n const handleLoad = React.useCallback(\n (event: React.SyntheticEvent<HTMLImageElement>) =>\n measure(event.currentTarget),\n [measure]\n )\n\n const supplied =\n item?.width && item?.height ? item.width / item.height : undefined\n // Discard a measurement left over from a previous `src`.\n const measuredRatio =\n measured && measured.src === src ? measured.ratio : undefined\n\n return {\n ratio: supplied ?? measuredRatio ?? RESERVED_SINGLE_ASPECT_RATIO,\n imgRef: measure,\n onLoad: handleLoad,\n }\n}\n\n/**\n * Sent / Received rendering — wrapped in the shared `Bubble` chrome,\n * supports single or stacked items, and renders an optional caption\n * below the media.\n */\nconst ImageBubbleRow: React.FC<InternalImageRowProps> = ({\n state,\n src,\n alt,\n filename,\n items,\n text,\n groupPosition,\n loading = 'lazy',\n onClick,\n}) => {\n const resolvedItems = resolveItems({ src, alt, items })\n const variant = bubbleVariantForState(state)\n const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(\n onClick\n )\n const isSingle = resolvedItems.length === 1\n const single = useSingleImageRatio(isSingle ? resolvedItems[0] : undefined)\n\n if (resolvedItems.length === 0) {\n return null\n }\n\n const tiles: MediaStackTile[] = resolvedItems.map((item, index) =>\n tileFromItem({\n item,\n index,\n totalCount: resolvedItems.length,\n fallbackLoading: loading,\n imgRef: isSingle ? single.imgRef : undefined,\n onLoad: isSingle ? single.onLoad : undefined,\n })\n )\n\n return (\n <Bubble\n variant={variant}\n text={text}\n groupPosition={groupPosition}\n // Media cards drop the 280px document width and the 8px inset\n // for a 2px frame around the media, at a uniform r20 that never\n // flattens against a caption bubble — `attachment-single-media`\n // 330×250 and `attachment-single-image` 383×290, both `pad2`\n // `cornerRadius: 20` with no stroke. The single card sizes to\n // the fitted media box (capped so a long in-bubble caption can\n // not stretch it past the 330px design maximum); the stacked\n // card is a fixed 383px, capped so it cannot overflow a narrow\n // viewport. `bordered={false}` because a hairline on top of the\n // 2px frame would read as a third edge and push the card 2px\n // over its measured width.\n bordered={false}\n widthClassName={\n isSingle\n ? 'w-fit max-w-[313px] sm:max-w-[330px]'\n : 'w-[383px] max-w-full'\n }\n paddingClassName=\"p-0.5\"\n radiusClassName=\"rounded-[20px]\"\n data-testid=\"image-attachment\"\n >\n <div className=\"relative\">\n <MediaStackGrid\n tiles={tiles}\n onTileActivate={handleActivate}\n singleAspectRatio={isSingle ? single.ratio : undefined}\n />\n </div>\n\n <ImageViewer\n open={viewerOpen}\n items={buildViewerItems(resolvedItems, filename)}\n initialIndex={viewerIndex}\n onClose={closeViewer}\n />\n </Bubble>\n )\n}\n\n/**\n * Composer-only props. Single image (`src`) is required; stacked\n * `items` and `text` captions are not supported in the composer state.\n */\nexport interface ImageComposerProps {\n src: string\n alt?: string\n filename?: string\n /**\n * Native lazy-load hint forwarded to the composer thumbnail `<img>`.\n * Defaults to `'lazy'`. See `ImageAttachmentSharedProps.loading` for\n * the rationale.\n */\n loading?: ImageLoadingMode\n onClick?: (index: number) => boolean | void\n onDismiss?: () => void\n}\n\nexport type ImageSentProps = ImageAttachmentSharedProps\nexport type ImageReceivedProps = ImageAttachmentSharedProps\n\nconst ImageComposer: React.FC<ImageComposerProps> = (props) => (\n <ImageComposerInner {...props} />\n)\nconst ImageSent: React.FC<ImageSentProps> = (props) => (\n <ImageBubbleRow {...props} state=\"sent\" />\n)\nconst ImageReceived: React.FC<ImageReceivedProps> = (props) => (\n <ImageBubbleRow {...props} state=\"received\" />\n)\n\nconst ImageAttachment = {\n Composer: ImageComposer,\n Sent: ImageSent,\n Received: ImageReceived,\n}\n\nexport default ImageAttachment\n","import React, { useMemo } from 'react'\n\nimport CarouselNav from './CarouselNav'\nimport { filenameFromUrl } from './fileMeta'\nimport { useCarousel } from './useCarousel'\nimport ViewerShell from './ViewerShell'\n\nexport interface PdfViewerItem {\n /** Source URL of the PDF document. */\n src: string\n /** Filename used as the dialog's accessible name. */\n filename?: string\n}\n\nexport interface PdfViewerProps {\n open: boolean\n items: PdfViewerItem[]\n /** Index to display when the viewer first opens. Defaults to `0`. */\n initialIndex?: number\n onClose: () => void\n}\n\n/**\n * Modal PDF viewer that hosts the active document inside a sandboxed\n * `<iframe>`, leaving native rendering, scroll, search, and the\n * browser's built-in download controls to the browser. The lightbox\n * chrome is intentionally just a close button plus, for stacked\n * attachments, carousel prev/next + counter.\n */\nconst PdfViewer: React.FC<PdfViewerProps> = ({\n open,\n items,\n initialIndex = 0,\n onClose,\n}) => {\n const { index, prev, next } = useCarousel({\n length: items.length,\n initialIndex,\n open,\n })\n\n const item = items[index]\n const filename = useMemo(\n () => item?.filename ?? (item ? filenameFromUrl(item.src) : 'document'),\n [item]\n )\n\n // `#toolbar=0` is honored by Chromium-family browsers and hides the\n // built-in PDF toolbar's redundant controls. Firefox / Safari ignore\n // it and keep their native toolbars, which is fine — the user gets a\n // familiar experience either way.\n //\n // Preserve any existing fragment the caller supplied (e.g.\n // `#page=3`) by merging our params into it rather than naively\n // appending a second `#…` segment.\n const iframeSrc = useMemo(\n () => (item ? withPdfViewerParams(item.src) : undefined),\n [item]\n )\n\n if (!item || !iframeSrc) return null\n\n return (\n <ViewerShell\n open={open}\n onClose={onClose}\n ariaLabel={filename}\n counter={\n items.length > 1 ? `${index + 1} / ${items.length}` : undefined\n }\n data-testid=\"pdf-viewer\"\n >\n <iframe\n // Force a key swap on item change so the iframe remounts a\n // fresh document rather than re-using the previous scroll\n // position / search state when the user navigates between\n // siblings.\n key={`${index}:${item.src}`}\n src={iframeSrc}\n title={filename}\n className=\"mes-media-viewer__iframe\"\n // Sandbox the iframe to stop the embedded document from\n // reaching parent context. We intentionally omit\n // `allow-same-origin`: even when the PDF host happens to share\n // our origin, granting it defeats the rest of the sandbox.\n // `allow-scripts` keeps the native PDF viewer's interactive\n // affordances (search, page nav, form filling) working,\n // `allow-downloads` lets the user save via the built-in\n // toolbar where it's still visible (Firefox / Safari), and\n // `allow-popups` lets external links inside the PDF open in a\n // new tab.\n sandbox=\"allow-scripts allow-forms allow-popups allow-downloads\"\n />\n\n {items.length > 1 ? (\n <CarouselNav\n onPrev={prev}\n onNext={next}\n prevLabel=\"Previous document\"\n nextLabel=\"Next document\"\n />\n ) : null}\n </ViewerShell>\n )\n}\n\n/**\n * Merge our PDF-viewer params (`toolbar=0`, `navpanes=0`) into the\n * URL's existing fragment so callers can still pass things like\n * `#page=3` without us clobbering them — naively appending\n * `#toolbar=0` to a URL that already has a fragment produces\n * `…#page=3#toolbar=0` which most readers ignore.\n */\nconst withPdfViewerParams = (src: string): string => {\n const hashIndex = src.indexOf('#')\n const base = hashIndex === -1 ? src : src.slice(0, hashIndex)\n const existing = hashIndex === -1 ? '' : src.slice(hashIndex + 1)\n\n const params = new URLSearchParams(existing)\n if (!params.has('toolbar')) params.set('toolbar', '0')\n if (!params.has('navpanes')) params.set('navpanes', '0')\n\n return `${base}#${params.toString()}`\n}\n\nexport default PdfViewer\n","import React from 'react'\n\nimport Bubble from '../_shared/Bubble'\nimport CompactDocumentRow from '../_shared/CompactDocumentRow'\nimport DismissButton from '../_shared/DismissButton'\nimport DownloadAction from '../_shared/DownloadAction'\nimport { filenameFromUrl } from '../_shared/fileMeta'\nimport PdfViewer, { type PdfViewerItem } from '../_shared/PdfViewer'\nimport { useViewer } from '../_shared/useViewer'\nimport {\n bubbleVariantForState,\n type BubbleVariant,\n type ComposerExtras,\n type MessageAttachmentBaseProps,\n type MessageAttachmentState,\n type PdfItem,\n} from '../types'\n\nexport interface PdfAttachmentSharedProps extends MessageAttachmentBaseProps {\n /** Single PDF — convenience for the most common case. */\n src?: string\n /** Filename — drives the row title + the meta line + the viewer dialog's accessible name. */\n filename?: string\n /** File size in bytes — formatted into the `EXT · SIZE` meta line. */\n fileSize?: number\n /**\n * Override displayed title (defaults to `filename`). Useful when a\n * sender re-titles a generated PDF before sending.\n */\n title?: string\n /**\n * Stacked PDFs. Takes precedence over `src` when set. Each item\n * renders as its own row inside the bubble; clicking a row opens\n * that PDF in the viewer. Sent + Received only — the composer\n * surface accepts a single attachment at a time.\n */\n items?: PdfItem[]\n /**\n * Forwarded to the viewer trigger. When omitted the click still\n * opens the built-in `PdfViewer`. Supply this to fire analytics, or\n * return `false` to intercept the open (e.g. swap to a route-based\n * viewer / confirmation modal). Any other return value (including\n * `void`/`undefined`) lets the built-in viewer open. For stacked\n * attachments the `index` argument identifies the row.\n */\n onClick?: (index: number) => boolean | void\n}\n\nconst resolveItems = ({\n src,\n filename,\n fileSize,\n title,\n items,\n}: {\n src?: string\n filename?: string\n fileSize?: number\n title?: string\n items?: PdfItem[]\n}): PdfItem[] => {\n if (items && items.length > 0) return items\n if (src) return [{ src, filename, fileSize, title }]\n return []\n}\n\ninterface PdfRowButtonProps {\n variant: BubbleVariant\n item: PdfItem\n index: number\n onActivate: (index: number) => void\n trailingAction?: React.ReactNode\n}\n\n/**\n * Single tappable PDF row — used both for the lone PDF case and for\n * each child of a stacked attachment. The icon + filename area opens\n * the viewer; the trailing slot hosts a separate, sibling button\n * (download or dismiss) so each affordance owns its own click target.\n */\nconst PdfRowButton: React.FC<PdfRowButtonProps> = ({\n variant,\n item,\n index,\n onActivate,\n trailingAction,\n}) => {\n const resolvedFilename = item.filename ?? filenameFromUrl(item.src)\n return (\n <CompactDocumentRow\n variant={variant}\n filename={resolvedFilename}\n title={item.title}\n mimeType=\"application/pdf\"\n fileSize={item.fileSize}\n onActivate={() => onActivate(index)}\n activateLabel={`Open ${resolvedFilename}`}\n trailingAction={trailingAction}\n />\n )\n}\n\ninterface InternalPdfRowProps extends PdfAttachmentSharedProps {\n state: MessageAttachmentState\n onDismiss?: () => void\n}\n\nconst PdfAttachmentRow: React.FC<InternalPdfRowProps> = ({\n state,\n src,\n filename,\n fileSize,\n title,\n items,\n text,\n groupPosition,\n onClick,\n onDismiss,\n}) => {\n const variant = bubbleVariantForState(state)\n const resolvedItems = resolveItems({ src, filename, fileSize, title, items })\n const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(\n onClick\n )\n const showDismiss = state === 'composer' && !!onDismiss\n\n if (resolvedItems.length === 0) {\n return null\n }\n\n // Project the bubble's `PdfItem`s onto the viewer's carousel-aware\n // shape. The viewer clamps its own active index defensively, so we\n // don't need to second-guess `viewerIndex` here.\n const viewerItems: PdfViewerItem[] = resolvedItems.map((item) => ({\n src: item.src,\n filename: item.filename ?? filenameFromUrl(item.src),\n }))\n\n return (\n <Bubble\n variant={variant}\n text={text}\n groupPosition={groupPosition}\n data-testid=\"pdf-attachment\"\n >\n {/* Stacked rows get a small vertical gap so each PDF reads as\n a discrete attachment rather than one merged blob. Single\n row falls through to a no-op `gap-0`. */}\n <div className=\"flex flex-col gap-2\">\n {resolvedItems.map((item, index) => {\n const itemFilename = item.filename ?? filenameFromUrl(item.src)\n // Composer only supports a single attachment so the dismiss\n // control sits on the only row. Sent / Received rows expose\n // a download icon button so the user can grab the PDF\n // without opening the viewer first — the viewer dialog is\n // intentionally just the document + a close button, so this\n // sibling action is the only one-click download surface.\n const trailingAction =\n showDismiss && index === 0 ? (\n <DismissButton onClick={onDismiss!} variant=\"inline\" />\n ) : state === 'composer' ? undefined : (\n <DownloadAction\n url={item.src}\n filename={itemFilename}\n variant=\"inline\"\n label={`Download ${itemFilename}`}\n tone={variant}\n />\n )\n return (\n <PdfRowButton\n key={`${item.src}-${index}`}\n variant={variant}\n item={item}\n index={index}\n onActivate={handleActivate}\n trailingAction={trailingAction}\n />\n )\n })}\n </div>\n\n <PdfViewer\n open={viewerOpen}\n items={viewerItems}\n initialIndex={viewerIndex}\n onClose={closeViewer}\n />\n </Bubble>\n )\n}\n\n/**\n * Composer-only props. Single PDF (`src`) only — the composer surface\n * accepts a single attachment at a time, so `items` is not supported.\n * Captions (`text`) and `groupPosition` are also dropped: the composer\n * renders a standalone draft, not part of a same-author run, and\n * captions live in the message-input textarea, not inside the draft\n * attachment preview.\n */\nexport interface PdfComposerProps extends ComposerExtras {\n src?: string\n filename?: string\n fileSize?: number\n title?: string\n onClick?: (index: number) => boolean | void\n}\nexport type PdfSentProps = PdfAttachmentSharedProps\nexport type PdfReceivedProps = PdfAttachmentSharedProps\n\nconst PdfComposer: React.FC<PdfComposerProps> = (props) => (\n <PdfAttachmentRow {...props} state=\"composer\" />\n)\nconst PdfSent: React.FC<PdfSentProps> = (props) => (\n <PdfAttachmentRow {...props} state=\"sent\" />\n)\nconst PdfReceived: React.FC<PdfReceivedProps> = (props) => (\n <PdfAttachmentRow {...props} state=\"received\" />\n)\n\nconst PdfAttachment = {\n Composer: PdfComposer,\n Sent: PdfSent,\n Received: PdfReceived,\n}\n\nexport default PdfAttachment\n","import React, { useMemo } from 'react'\n\nimport type { MediaPreloadMode } from '../types'\n\nimport CarouselNav from './CarouselNav'\nimport { filenameFromUrl } from './fileMeta'\nimport { useCarousel } from './useCarousel'\nimport ViewerShell from './ViewerShell'\n\nexport interface VideoViewerItem {\n src: string\n poster?: string\n mimeType?: string\n filename?: string\n /**\n * Per-item `<video preload>` override. Defaults to `'metadata'`\n * for the active item so duration / first-frame are available\n * immediately. Non-active siblings aren't mounted here (we render\n * only `items[index]` at a time via a `key` swap), so they\n * naturally stay unloaded.\n */\n preload?: MediaPreloadMode\n}\n\nexport interface VideoViewerProps {\n open: boolean\n items: VideoViewerItem[]\n /** Index to display when the viewer first opens. Defaults to `0`. */\n initialIndex?: number\n onClose: () => void\n}\n\n/**\n * Full-viewport video viewer used by every `MessageAttachment.Video.*`\n * variant. Renders a single `<video controls>` element with the\n * browser's native chrome (play / pause / seek / fullscreen / volume /\n * download).\n *\n * When `items.length > 1` the viewer becomes a carousel — see\n * `ImageViewer` for the same behavior; arrow-key navigation yields to\n * the focused `<video>` so the native seek shortcuts still work.\n */\nconst VideoViewer: React.FC<VideoViewerProps> = ({\n open,\n items,\n initialIndex = 0,\n onClose,\n}) => {\n const { index, prev, next } = useCarousel({\n length: items.length,\n initialIndex,\n open,\n })\n\n const item = items[index]\n const filename = useMemo(\n () => item?.filename ?? (item ? filenameFromUrl(item.src) : 'video'),\n [item]\n )\n\n if (!item) return null\n\n return (\n <ViewerShell\n open={open}\n onClose={onClose}\n ariaLabel={filename}\n counter={\n items.length > 1 ? `${index + 1} / ${items.length}` : undefined\n }\n data-testid=\"video-viewer\"\n >\n {/* No `<track>` is rendered — we don't author caption sidecars\n for chat attachments, and an empty `<track kind=\"captions\" />`\n emits a runtime warning. Re-add a real track (with `src`,\n `srcLang`, and `default`) once caption support ships. */}\n <video\n // Forcing a key swap on item change ensures the new source\n // mounts a fresh element instead of reusing the previous\n // playback state — keeps the video paused-at-start when\n // navigating between stacked items.\n key={`${index}:${item.src}`}\n src={item.src}\n poster={item.poster}\n controls\n // `autoPlay` without `muted` is blocked by Chrome / Safari /\n // Firefox autoplay policies — the click that opens the viewer\n // doesn't count as a user-gesture for the freshly-mounted\n // `<video>` element. Defaulting to muted means playback\n // actually starts (matches how IG / Slack handle their video\n // lightboxes); the user can unmute via the native controls if\n // they want sound.\n autoPlay\n muted\n playsInline\n preload={item.preload ?? 'metadata'}\n className=\"mes-media-viewer__video\"\n >\n {item.mimeType ? <source src={item.src} type={item.mimeType} /> : null}\n </video>\n\n {items.length > 1 ? (\n <CarouselNav\n onPrev={prev}\n onNext={next}\n prevLabel=\"Previous video\"\n nextLabel=\"Next video\"\n />\n ) : null}\n </ViewerShell>\n )\n}\n\nexport default VideoViewer\n","import { PlayIcon, VideoCameraIcon } from '@phosphor-icons/react'\nimport React from 'react'\n\nimport Bubble from '../_shared/Bubble'\nimport DismissButton from '../_shared/DismissButton'\nimport MediaStackGrid, {\n type MediaStackTile,\n} from '../_shared/MediaStackGrid'\nimport { useViewer } from '../_shared/useViewer'\nimport VideoViewer, { type VideoViewerItem } from '../_shared/VideoViewer'\nimport {\n bubbleVariantForState,\n type MediaPreloadMode,\n type MessageAttachmentBaseProps,\n type MessageAttachmentState,\n type VideoItem,\n} from '../types'\n\nexport interface VideoAttachmentSharedProps extends MessageAttachmentBaseProps {\n /** Single video — convenience for the most common case. */\n src?: string\n /** Poster image — preview shown before playback starts. */\n poster?: string\n /** MIME type hint — typed onto the inline `<source>` element. */\n mimeType?: string\n /** Filename used as the viewer dialog's accessible name. */\n filename?: string\n /**\n * Stacked videos. Takes precedence over `src` when set. Renders a\n * 1 / 2 / 3 / 4-tile grid (5+ collapse into a `+N` overflow tile).\n * Sent + Received only — the composer surface intentionally accepts\n * a single attachment at a time.\n */\n items?: VideoItem[]\n /**\n * `<video preload>` hint forwarded into the viewer. Defaults to\n * `'none'` — the poster `<img>` carries the visual weight on the\n * bubble surface, and we shouldn't fetch any video bytes until the\n * user actually opens the viewer. Per-item overrides live on\n * `VideoItem.preload`. The opened `VideoViewer` always preloads\n * metadata for the active item (so duration / first-frame appear\n * immediately) regardless of this value.\n */\n preload?: MediaPreloadMode\n /**\n * Forwarded to the viewer trigger. When omitted the click still\n * opens the built-in `VideoViewer` — supply this for analytics, or\n * return `false` to intercept the open (e.g. switch to a route-\n * based player / confirmation modal). Any other return value\n * (including `void`/`undefined`) lets the built-in viewer open.\n * For stacked attachments the `index` argument identifies the tile.\n */\n onClick?: (index: number) => boolean | void\n}\n\nconst PlayBadge: React.FC = () => (\n <div className=\"pointer-events-none absolute inset-0 flex items-center justify-center\">\n <span className=\"flex size-12 items-center justify-center rounded-full bg-black/55 text-white backdrop-blur\">\n <PlayIcon className=\"size-6\" weight=\"fill\" aria-hidden />\n </span>\n </div>\n)\n\n// The tile shell owns the cluster's geometry: `MediaStackGrid` gives\n// each tile the 18px radius on the cluster's OUTER corners only and\n// clips with `overflow-hidden`, so a poster must carry no radius of\n// its own. A `rounded-md` here would round all four corners of every\n// tile at 6px, cutting dark wedges out of the `bg-[#0d0d0d]` backing\n// at each interior grid junction.\nconst PosterTile: React.FC<{ item: VideoItem; index: number }> = ({\n item,\n index,\n}) => (\n <div className=\"absolute inset-0 size-full bg-[#0d0d0d]\">\n {item.poster ? (\n <img\n src={item.poster}\n alt={`Video ${index + 1} thumbnail`}\n draggable={false}\n loading=\"lazy\"\n decoding=\"async\"\n className=\"absolute inset-0 size-full object-cover\"\n />\n ) : (\n <div className=\"absolute inset-0 flex items-center justify-center\">\n <VideoCameraIcon\n className=\"size-16 text-white/30\"\n weight=\"regular\"\n aria-hidden\n />\n </div>\n )}\n <PlayBadge />\n </div>\n)\n\nconst tileFromItem = (\n item: VideoItem,\n index: number,\n totalCount: number\n): MediaStackTile => ({\n ariaLabel: `Play video ${index + 1} of ${totalCount}`,\n content: <PosterTile item={item} index={index} />,\n})\n\nconst resolveItems = ({\n src,\n poster,\n mimeType,\n preload,\n items,\n}: {\n src?: string\n poster?: string\n mimeType?: string\n preload?: MediaPreloadMode\n items?: VideoItem[]\n}): VideoItem[] => {\n if (items && items.length > 0) {\n return preload ? items.map((it) => ({ ...it, preload: it.preload ?? preload })) : items\n }\n if (src) return [{ src, poster, mimeType, preload }]\n return []\n}\n\ninterface InternalVideoRowProps extends VideoAttachmentSharedProps {\n state: MessageAttachmentState\n}\n\n/**\n * Project the bubble's `VideoItem`s onto the carousel-aware viewer's\n * `VideoViewerItem` shape. When a shared outer `filename` is supplied\n * for a stacked bubble, suffix `(N)` per sibling so each carousel page\n * still gets a distinct accessible name.\n */\nconst buildViewerItems = (\n resolvedItems: VideoItem[],\n filename?: string\n): VideoViewerItem[] =>\n resolvedItems.map((item, index) => ({\n src: item.src,\n poster: item.poster,\n mimeType: item.mimeType,\n preload: item.preload,\n filename:\n filename && resolvedItems.length === 1\n ? filename\n : filename\n ? `${filename} (${index + 1})`\n : undefined,\n }))\n\n/**\n * Composer rendering — bare 280px-square poster with a play badge,\n * a dismiss `×` overlay, and `rounded-md` corners. Renders without the\n * shared `Bubble` chrome (no border / no background / no padding) so\n * the in-progress attachment looks like a draft preview rather than a\n * sent message. Single attachment only — `items` and `text` are not\n * supported on the composer surface.\n */\nconst VideoComposerInner: React.FC<{\n src: string\n poster?: string\n mimeType?: string\n filename?: string\n preload?: MediaPreloadMode\n onClick?: (index: number) => boolean | void\n onDismiss?: () => void\n}> = ({ src, poster, mimeType, filename, preload, onClick, onDismiss }) => {\n const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(\n onClick\n )\n\n return (\n <div className=\"relative w-fit\">\n <button\n type=\"button\"\n onClick={() => handleActivate(0)}\n aria-label=\"Play video\"\n className=\"relative block size-[280px] cursor-pointer overflow-hidden rounded-md outline-none focus-visible:ring-2 focus-visible:ring-black/40\"\n >\n <PosterTile item={{ src, poster, mimeType }} index={0} />\n </button>\n {onDismiss ? (\n <div className=\"absolute right-2 top-2 z-10\">\n <DismissButton onClick={onDismiss} />\n </div>\n ) : null}\n\n <VideoViewer\n open={viewerOpen}\n items={buildViewerItems(\n [{ src, poster, mimeType, preload }],\n filename\n )}\n initialIndex={viewerIndex}\n onClose={closeViewer}\n />\n </div>\n )\n}\n\n/**\n * Sent / Received rendering — wrapped in the shared `Bubble` chrome,\n * supports single or stacked items, and renders an optional caption\n * below the media.\n */\nconst VideoBubbleRow: React.FC<InternalVideoRowProps> = ({\n state,\n src,\n poster,\n mimeType,\n filename,\n items,\n text,\n groupPosition,\n preload,\n onClick,\n}) => {\n const resolvedItems = resolveItems({ src, poster, mimeType, preload, items })\n const variant = bubbleVariantForState(state)\n const { viewerOpen, viewerIndex, handleActivate, closeViewer } = useViewer(\n onClick\n )\n\n if (resolvedItems.length === 0) {\n return null\n }\n\n const tiles: MediaStackTile[] = resolvedItems.map((item, index) =>\n tileFromItem(item, index, resolvedItems.length)\n )\n\n return (\n <Bubble\n variant={variant}\n text={text}\n groupPosition={groupPosition}\n data-testid=\"video-attachment\"\n >\n <div className=\"relative\">\n <MediaStackGrid tiles={tiles} onTileActivate={handleActivate} />\n </div>\n\n <VideoViewer\n open={viewerOpen}\n items={buildViewerItems(resolvedItems, filename)}\n initialIndex={viewerIndex}\n onClose={closeViewer}\n />\n </Bubble>\n )\n}\n\n/**\n * Composer-only props. Single video (`src`) is required; stacked\n * `items` and `text` captions are not supported in the composer state.\n */\nexport interface VideoComposerProps {\n src: string\n poster?: string\n mimeType?: string\n filename?: string\n /**\n * `<video preload>` hint forwarded into the viewer. Defaults to\n * `'none'`. See `VideoAttachmentSharedProps.preload`.\n */\n preload?: MediaPreloadMode\n onClick?: (index: number) => boolean | void\n onDismiss?: () => void\n}\n\nexport type VideoSentProps = VideoAttachmentSharedProps\nexport type VideoReceivedProps = VideoAttachmentSharedProps\n\nconst VideoComposer: React.FC<VideoComposerProps> = (props) => (\n <VideoComposerInner {...props} />\n)\nconst VideoSent: React.FC<VideoSentProps> = (props) => (\n <VideoBubbleRow {...props} state=\"sent\" />\n)\nconst VideoReceived: React.FC<VideoReceivedProps> = (props) => (\n <VideoBubbleRow {...props} state=\"received\" />\n)\n\nconst VideoAttachment = {\n Composer: VideoComposer,\n Sent: VideoSent,\n Received: VideoReceived,\n}\n\nexport default VideoAttachment\n","import AudioAttachment, {\n type AudioComposerProps,\n type AudioReceivedProps,\n type AudioSentProps,\n} from './Audio'\nimport FileAttachment, {\n type FileComposerProps,\n type FileReceivedProps,\n type FileSentProps,\n} from './File'\nimport ImageAttachment, {\n type ImageComposerProps,\n type ImageReceivedProps,\n type ImageSentProps,\n} from './Image'\nimport PdfAttachment, {\n type PdfComposerProps,\n type PdfReceivedProps,\n type PdfSentProps,\n} from './Pdf'\nimport VideoAttachment, {\n type VideoComposerProps,\n type VideoReceivedProps,\n type VideoSentProps,\n} from './Video'\n\n/**\n * Per-MIME chat attachment cards used inside the messaging stream.\n *\n * Each subnamespace (`Image`, `Video`, `Audio`, `Pdf`, `File`) exposes\n * three states (`Composer`, `Sent`, `Received`) that map to the\n * sender / recipient sides of a chat:\n *\n * - `Composer` — sender draft state with a dismiss `×` affordance.\n * - `Sent` — sender-side state shown after posting.\n * - `Received` — recipient-side state.\n *\n * Sent / Received variants accept an optional `text` prop that renders\n * inside the same bubble as the attachment — mirroring the mobile chat\n * where a sender can pair a caption with an attachment ('Here is the\n * file', 'Here is the image'). Composer prop types intentionally omit\n * `text` (and `items` / `groupPosition`) so the type system enforces\n * the same contract: captions live in the message-input textarea, not\n * inside the draft attachment preview.\n *\n * Stacked attachments — every type except `Composer` accepts an\n * `items={[…]}` prop on Sent / Received that takes precedence over\n * the singular `src`/`filename`/etc. inputs:\n *\n * - Image / Video render a 1 / 2 / 3 / 4-tile grid (5+ collapse\n * into a `+N` overflow tile). Activating any tile opens the\n * built-in `ImageViewer` / `VideoViewer` at that index; when\n * `items.length > 1` the lightbox becomes a carousel (prev / next\n * chrome, ArrowLeft / ArrowRight, current / total counter) so the\n * user can browse siblings without closing the modal.\n * - Pdf / Audio / File render their compact rows / players in a\n * vertical stack with an 8px gap between rows. Each row keeps\n * its own activation target — Pdf opens the carousel-aware\n * `PdfViewer` at that index, Audio gets its own native player,\n * and File downloads the asset for that row.\n *\n * The Composer surface accepts only a single attachment at a time,\n * so its props omit `items` / `text`.\n *\n * Image / Video Composer renders bare — just the media with rounded\n * corners and a dismiss overlay, no surrounding bubble — so the draft\n * preview reads as an in-progress attachment instead of a sent\n * message. Sent / Received wrap the media in the shared bubble chrome.\n *\n * Image / Video / Pdf are interactive in every state (Composer, Sent,\n * Received) — clicks open the corresponding native lightbox `<dialog>`,\n * which centers the media at reasonable max dimensions (Escape and\n * backdrop click also dismiss). The Image viewer adds an explicit\n * download button to the chrome; the Video viewer relies on the\n * native `<video controls>` download menu, and the Pdf viewer\n * inherits the browser's built-in PDF download. Sibling-row Download\n * buttons on Pdf / File and the native `<audio>` controls expose\n * downloads outside the viewer too. The lightbox chrome is styled\n * with plain CSS (`.mes-media-viewer*`) so the package's shipped\n * stylesheet is enough to render the viewer correctly without a\n * Tailwind dependency in the consumer.\n *\n * # Lazy-loading defaults\n *\n * Media attachments bake in network-friendly defaults for long chat\n * histories and expose explicit escape hatches when a caller knows\n * better (e.g. a hero attachment that's already on-screen):\n *\n * - **Image** — every `<img>` rendered on the bubble surface\n * (Composer thumbnail, single + stacked tiles, `+N` overflow)\n * ships with `loading=\"lazy\" decoding=\"async\"`. Override via the\n * attachment's `loading` prop (or `ImageItem.loading` for per-tile\n * control). The opened `ImageViewer` always eager-loads the\n * active image so it appears immediately.\n * - **Video** — the `<video>` rendered inside the `VideoViewer`\n * defaults to `preload=\"none\"`, so no video bytes are fetched\n * until the user opens the viewer. Poster `<img>` thumbnails on\n * the bubble surface are `loading=\"lazy\" decoding=\"async\"`.\n * Override via the attachment's `preload` prop (or\n * `VideoItem.preload` per item). Once the viewer is open the\n * active item bumps to `preload=\"metadata\"` so duration / the\n * first frame appear immediately.\n * - **Audio** — a single audio bubble defaults to\n * `preload=\"metadata\"` so the native player surfaces duration\n * immediately. A stacked audio bubble (`items.length > 1`)\n * defaults to `preload=\"none\"` for every player so a thread of\n * voice memos doesn't fan out N parallel metadata requests on\n * first paint. Override via the attachment's `preload` prop\n * (or `AudioItem.preload` per track).\n * - **Pdf** / **File** — no inline media element to throttle; the\n * bubble renders compact rows that download / open lazily on\n * activation. No `loading` / `preload` props on these surfaces.\n */\nconst MessageAttachment = {\n Image: ImageAttachment,\n Video: VideoAttachment,\n Audio: AudioAttachment,\n Pdf: PdfAttachment,\n File: FileAttachment,\n}\n\nexport default MessageAttachment\n\nexport type {\n ImageComposerProps,\n ImageSentProps,\n ImageReceivedProps,\n VideoComposerProps,\n VideoSentProps,\n VideoReceivedProps,\n AudioComposerProps,\n AudioSentProps,\n AudioReceivedProps,\n PdfComposerProps,\n PdfSentProps,\n PdfReceivedProps,\n FileComposerProps,\n FileSentProps,\n FileReceivedProps,\n}\nexport type {\n ImageItem,\n ImageLoadingMode,\n VideoItem,\n PdfItem,\n AudioItem,\n FileItem,\n BubbleGroupPosition,\n MediaPreloadMode,\n MessageAttachmentBaseProps,\n MessageAttachmentState,\n} from './types'\nexport { bubbleGroupPositionFromStream } from './types'\nexport {\n formatFileSize,\n getFileExtensionLabel,\n buildCompactMetaLabel,\n} from './_shared/fileMeta'\n","import { createContext, useState } from 'react'\n\nimport type { BroadcastNameResolver } from '../../types'\n\n/** Live campaign names keyed by broadcast id — not the resolver function. */\nexport type BroadcastNamesById = Readonly<Record<string, string>>\n\nexport const BroadcastNameContext = createContext<\n BroadcastNamesById | undefined\n>(undefined)\n\nconst broadcastIdFromMessage = (message: {\n metadata?: { broadcast_id?: unknown }\n}): string | undefined => {\n const broadcastId = message.metadata?.broadcast_id\n return typeof broadcastId === 'string' ? broadcastId : undefined\n}\n\n/**\n * Resolve campaign names for the messages currently in view.\n *\n * Built outside memoized Stream rows so a new resolver reference can publish\n * new campaign names without remounting the Stream message rows.\n */\nexport const buildBroadcastNamesById = (\n messages: readonly { metadata?: { broadcast_id?: unknown } }[] | undefined,\n resolveBroadcastName: BroadcastNameResolver | undefined\n): BroadcastNamesById | undefined => {\n if (!resolveBroadcastName || !messages?.length) {\n return undefined\n }\n\n const seen = new Set<string>()\n let names: Record<string, string> | undefined\n\n for (const message of messages) {\n const broadcastId = broadcastIdFromMessage(message)\n if (!broadcastId || seen.has(broadcastId)) {\n continue\n }\n seen.add(broadcastId)\n\n const resolved = resolveBroadcastName(broadcastId)\n if (!resolved) {\n continue\n }\n\n names ??= {}\n names[broadcastId] = resolved\n }\n\n return names\n}\n\nconst areBroadcastNamesEqual = (\n previous: BroadcastNamesById | undefined,\n next: BroadcastNamesById | undefined\n): boolean => {\n if (previous === next) {\n return true\n }\n if (!previous || !next) {\n return false\n }\n\n const previousIds = Object.keys(previous)\n const nextIds = Object.keys(next)\n if (previousIds.length !== nextIds.length) {\n return false\n }\n\n return previousIds.every((id) => previous[id] === next[id])\n}\n\n/**\n * Re-invokes the resolver on every render, but only returns a new object when\n * the resolved strings actually change — so context consumers update for\n * late-arriving campaign names without thrashing on unrelated renders.\n */\nexport const useBroadcastNamesById = (\n messages: readonly { metadata?: { broadcast_id?: unknown } }[] | undefined,\n resolveBroadcastName: BroadcastNameResolver | undefined\n): BroadcastNamesById | undefined => {\n const names = buildBroadcastNamesById(messages, resolveBroadcastName)\n const [publishedNames, setPublishedNames] = useState<\n BroadcastNamesById | undefined\n >(names)\n\n if (!areBroadcastNamesEqual(publishedNames, names)) {\n setPublishedNames(names)\n return names\n }\n\n return publishedNames\n}\n","import type { Channel, LocalMessage } from 'stream-chat'\n\nimport type { PaymentStatus } from '../../../stream-custom-data'\n\nexport interface LockedAttachmentGalleryItem {\n /** Drives the per-item play/lock affordance. */\n mimeType: string\n /** Poster image, locked or unlocked. */\n thumbnailUrl?: string\n /** Media source, shown only when unlocked. */\n sourceUrl?: string\n}\n\nexport interface LockedAttachmentSource {\n /** Proxied URL for in-app playback. */\n sourceUrl: string\n /** Opened when the visitor clicks Download. */\n redeemUrl?: string\n /** Overrides the metadata thumbnail when present. */\n thumbnailUrl?: string\n}\n\nexport type { PaymentStatus }\n\n/** Fetches the underlying source; `void` return means none available. */\nexport type FetchSourceHandler = () => Promise<LockedAttachmentSource | void>\n\nexport interface LockedAttachmentContextValue {\n /** Whether the given message is mid-checkout. */\n isUnlocking: (id: string) => boolean\n /** Opens the checkout flow for a message. */\n onUnlockClick?: (message: LocalMessage, channel: Channel) => void\n /** Handles a Download click for a message. */\n onDownloadClick?: (message: LocalMessage, channel: Channel) => void\n /** Reveals the locked media's source. Unused for text — payment writes `message.text` directly. */\n onFetchSource?: (\n message: LocalMessage,\n channel: Channel\n ) => Promise<LockedAttachmentSource | void>\n}\n\nexport const defaultLockedAttachmentContextValue: LockedAttachmentContextValue = {\n isUnlocking: () => false,\n}\n","import { createContext, useContext } from 'react'\n\nimport { defaultLockedAttachmentContextValue, type LockedAttachmentContextValue } from './LockedAttachment/types'\n\nexport interface CustomMessageRegistry {\n LockedAttachment: LockedAttachmentContextValue\n}\n\nconst customMessageDefaults: CustomMessageRegistry = {\n LockedAttachment: defaultLockedAttachmentContextValue,\n}\n\nconst CustomMessageContext = createContext<Partial<CustomMessageRegistry>>({})\n\nexport const CustomMessageProvider = CustomMessageContext.Provider\n\nexport function useCustomMessage<K extends keyof CustomMessageRegistry>(key: K): CustomMessageRegistry[K] {\n const ctx = useContext(CustomMessageContext)\n return (ctx[key] ?? customMessageDefaults[key]) as CustomMessageRegistry[K]\n}\n","import React, { Suspense } from 'react'\n\nimport type {\n MediaCardDraftProps,\n MediaCardOtherProps,\n MediaCardOwnProps,\n MediaCardPreviewProps,\n} from './components/Media/types'\nimport type {\n TextCardOtherProps,\n TextCardOwnProps,\n} from './components/Text/types'\n\nconst TextCardLazy = React.lazy(() => import('./components/Text/Card'))\nconst MediaCardLazy = React.lazy(() => import('./components/Media/Card'))\n\nconst LockedAttachmentFallback = () => (\n <div\n className=\"w-[280px] min-h-[200px] animate-pulse rounded-md bg-black/[0.06] shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_1px_2px_rgba(0,0,0,0.04),0_8px_32px_rgba(0,0,0,0.1)]\"\n aria-hidden\n />\n)\n\nexport type DraftProps = MediaCardDraftProps & {\n /** Drafting is always the sender — doesn't apply here. */\n isMine?: never\n /**\n * \"text\" renders as a chat bubble showing the real draft `text` instead of\n * a media thumbnail card. Media-only fields below are ignored in text mode.\n * Defaults to `\"media\"`.\n */\n contentType?: 'text' | 'media'\n /** The message's own text, shown as-is (not redacted yet). Only used when `contentType` is `\"text\"`. */\n renderedText?: React.ReactNode\n}\n\nexport type PreviewProps = MediaCardPreviewProps & {\n /** Drafting is always the sender — doesn't apply here. */\n isMine?: never\n /**\n * \"text\" renders as a chat bubble instead of a media thumbnail card.\n * Media-only fields below are ignored in text mode. Defaults to `\"media\"`.\n */\n contentType?: 'text' | 'media'\n}\n\nexport type SentTextOwnProps = TextCardOwnProps & {\n contentType: 'text'\n /** Sender's view has no unlock flow — doesn't apply here. */\n onUnlockClick?: never\n isUnlocking?: never\n}\n\nexport type SentTextOtherProps = TextCardOtherProps & {\n contentType: 'text'\n}\n\nexport type SentMediaOwnProps = MediaCardOwnProps & {\n contentType?: 'media'\n /** Sender's view has no unlock/download flow — doesn't apply here. */\n onUnlockClick?: never\n isUnlocking?: never\n onDownloadClick?: never\n}\n\nexport type SentMediaOtherProps = MediaCardOtherProps & {\n contentType?: 'media'\n}\n\n/**\n * The card for a paid attachment — a still-drafting `isDraft` card, a static\n * receiver-facing `isPreview` mockup, or a sent text/media card discriminated\n * by `isMine` + `contentType`. Internally lazy-loads only the one underlying\n * implementation the resolved branch needs.\n */\nexport type LockedAttachmentProps =\n | DraftProps\n | PreviewProps\n | SentTextOwnProps\n | SentTextOtherProps\n | SentMediaOwnProps\n | SentMediaOtherProps\n\nconst LockedAttachment: React.FC<LockedAttachmentProps> = (props) => {\n return (\n <Suspense fallback={<LockedAttachmentFallback />}>\n {props.contentType === 'text' ? (\n <TextCardLazy {...props} />\n ) : (\n <MediaCardLazy {...props} />\n )}\n </Suspense>\n )\n}\n\nexport default LockedAttachment\nexport { default as PurchaseStatusBadge } from './components/_shared/PurchaseStatusBadge'\nexport type { PurchaseStatusBadgeProps } from './components/_shared/PurchaseStatusBadge'\nexport type {\n PaymentStatus,\n FetchSourceHandler,\n LockedAttachmentSource,\n LockedAttachmentGalleryItem,\n LockedAttachmentContextValue,\n} from './types'\n","import { MESSAGE_CUSTOM_TYPE } from '@linktr.ee/messaging-taxonomy'\nimport { GiftIcon } from '@phosphor-icons/react'\nimport { LocalMessage } from 'stream-chat'\n\ninterface MessageTagProps {\n message: LocalMessage\n /** When true, renders as a standalone bubble instead of a small tag */\n standalone?: boolean\n /** When true, renders sender-side chatbot variants */\n isMyMessage?: boolean\n /** Whether the message includes an attachment */\n hasAttachment?: boolean\n}\n\nconst SparkleIcon = ({ size = 15 }: { size?: number }) => (\n <svg\n width={size}\n height={size}\n viewBox=\"0 0 15 15\"\n fill=\"none\"\n aria-hidden=\"true\"\n >\n <path\n d=\"M12.003 9a.985.985 0 0 1-.652.934l-3.223 1.191-1.188 3.226a.995.995 0 0 1-1.867 0l-1.195-3.226L.65 9.937a.995.995 0 0 1 0-1.867l3.227-1.195 1.187-3.226a.995.995 0 0 1 1.868 0l1.195 3.226 3.226 1.187a.99.99 0 0 1 .649.938m3-5.83a.52.52 0 0 1-.344.492l-1.702.63-.627 1.703a.525.525 0 0 1-.986 0l-.63-1.704-1.704-.627a.525.525 0 0 1 0-.986l1.703-.63.627-1.704a.526.526 0 0 1 .986 0l.631 1.703 1.704.627a.52.52 0 0 1 .342.495\"\n fill=\"currentColor\"\n fillOpacity={0.55}\n />\n </svg>\n)\n\n/** Check if a message is a tip based on metadata */\nexport const isTipMessage = (message: LocalMessage): boolean => {\n return message.metadata?.custom_type === MESSAGE_CUSTOM_TYPE.TIP\n}\n\n/** Check if a message is a paid message based on metadata */\nexport const isPaidMessage = (message: LocalMessage): boolean => {\n return message.metadata?.custom_type === MESSAGE_CUSTOM_TYPE.PAID\n}\n\n/** Check if a message is a chatbot message based on metadata */\nexport const isChatbotMessage = (message: LocalMessage): boolean => {\n return message.metadata?.custom_type === MESSAGE_CUSTOM_TYPE.CHATBOT\n}\n\n/** Check if a message is a locked attachment */\nexport const isAttachmentMessage = (message: LocalMessage): boolean => {\n return message.metadata?.custom_type === MESSAGE_CUSTOM_TYPE.ATTACHMENT\n}\n\nexport const isTextAttachmentMessage = (message: LocalMessage): boolean => {\n return (\n isAttachmentMessage(message) &&\n message.metadata?.attachment_content_type === 'text'\n )\n}\n\nexport const isMediaAttachmentMessage = (message: LocalMessage): boolean => {\n return (\n isAttachmentMessage(message) &&\n message.metadata?.attachment_content_type !== 'text'\n )\n}\n\n/** Check if a message has a tip or paid tag (shared layout, distinct copy) */\nexport const isTipOrPaidMessage = (message: LocalMessage): boolean => {\n return isTipMessage(message) || isPaidMessage(message)\n}\n\n/** Check if a message is a tip/paid-only message (no text) */\nexport const isTipOnlyMessage = (message: LocalMessage): boolean => {\n return isTipOrPaidMessage(message) && !message.text?.trim()\n}\n\nexport const MessageTag = ({\n message,\n standalone = false,\n isMyMessage = false,\n hasAttachment = false,\n}: MessageTagProps) => {\n const isTipOrPaid = isTipOrPaidMessage(message)\n const isChatbot = isChatbotMessage(message)\n\n if (!isTipOrPaid && !isChatbot) {\n return null\n }\n\n if (isTipOrPaid) {\n const amountText = message.metadata?.amount_text\n if (!amountText) return null\n\n const className = standalone\n ? 'message-tip-standalone'\n : 'message-tag message-tag--tip'\n\n const amountLabel = isPaidMessage(message) ? 'message' : 'tip'\n const label = standalone\n ? `${amountText} ${amountLabel}`\n : `Delivered with ${amountText} ${amountLabel}`\n\n return (\n <div className={className}>\n <GiftIcon size={standalone ? 14 : 12} />\n <span>{label}</span>\n </div>\n )\n }\n\n const isSenderAttachmentVariant = isMyMessage && hasAttachment\n const chatbotLabel = isSenderAttachmentVariant\n ? 'Sent with AI'\n : 'Sent by AI Assistant'\n\n const chatbotClassName = [\n 'message-chatbot-indicator',\n isMyMessage\n ? 'message-chatbot-indicator--sender'\n : 'message-chatbot-indicator--receiver',\n isSenderAttachmentVariant\n ? 'message-chatbot-indicator--attachment'\n : 'message-chatbot-indicator--text',\n ].join(' ')\n\n const label = (\n <span className=\"message-chatbot-indicator__label\">{chatbotLabel}</span>\n )\n const icon = (\n <span className=\"message-chatbot-indicator__icon\">\n <SparkleIcon size={isSenderAttachmentVariant ? 12 : 15} />\n </span>\n )\n\n // Chatbot indicator variant\n return (\n <div className={chatbotClassName} data-testid=\"message-chatbot-indicator\">\n {isMyMessage && !isSenderAttachmentVariant ? (\n <>\n {label}\n {icon}\n </>\n ) : (\n <>\n {icon}\n {label}\n </>\n )}\n </div>\n )\n}\n","import { ThumbsUpIcon, ThumbsDownIcon } from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React from 'react'\n\nimport type { VoteSelection } from '../../hooks/useMessageVote'\n\ninterface MessageVoteButtonsProps {\n selected: VoteSelection\n onVoteUp: () => void\n onVoteDown: () => void\n}\n\nexport const MessageVoteButtons: React.FC<MessageVoteButtonsProps> = ({\n selected,\n onVoteUp,\n onVoteDown,\n}) => (\n <div className=\"message-vote-buttons\">\n <button\n type=\"button\"\n className={classNames('message-vote-button focus-ring', {\n 'message-vote-button--selected': selected === 'up',\n })}\n onClick={onVoteUp}\n aria-label=\"Good response\"\n aria-pressed={selected === 'up'}\n data-tooltip=\"Good response\"\n >\n <ThumbsUpIcon size={16} weight={selected === 'up' ? 'fill' : 'regular'} />\n </button>\n <button\n type=\"button\"\n className={classNames('message-vote-button focus-ring', {\n 'message-vote-button--selected': selected === 'down',\n })}\n onClick={onVoteDown}\n aria-label=\"Bad response\"\n aria-pressed={selected === 'down'}\n data-tooltip=\"Bad response\"\n >\n <ThumbsDownIcon size={16} weight={selected === 'down' ? 'fill' : 'regular'} />\n </button>\n </div>\n)\n","import {\n ChecksIcon,\n CircleNotchIcon,\n MegaphoneIcon,\n WarningCircleIcon,\n} from '@phosphor-icons/react'\nimport {\n MessageStatus,\n useChannelStateContext,\n useMessageContext,\n} from 'stream-chat-react'\n\n/**\n * Message status stamps for sending and terminal `MessageStatus` states\n * (sent / delivered / read), plus failed messages.\n *\n * Two reasons this is more than a static label:\n *\n * 1. `MessageStatus` picks its `delivered`/`read` branches ahead of `sent`\n * whenever Stream has `deliveredTo`/`readBy`, and — unlike `sent` — those\n * branches aren't gated to the last message. So we override all three\n * terminal slots with this same label (never Stream's default delivered\n * icon / read avatars — Read is out of scope) and re-apply the \"latest\n * message in the thread\" gate ourselves.\n *\n * 2. We derive \"latest message\" from **live channel state**, not the message\n * context's `lastOwnMessage`/`lastReceivedId`. Stream's `Message` is\n * memoised by `areMessagePropsEqual`, which does not compare those props;\n * a previous own row whose message/grouping props are unchanged (e.g.\n * after an intervening incoming message) never re-renders, so the old\n * bubble would keep showing \"Delivered\" beside the new one.\n * `useChannelStateContext` gets a fresh `messages` array on every new\n * message and, being a context, re-renders this leaf straight through the\n * stale `Message`/`MessageStatus` memos — so the label reliably clears\n * once a reply lands. We still skip non-participant rows (`system`,\n * `ephemeral`, `deleted`) when finding the last real message so a trailing\n * system event does not clear the label.\n *\n * Rendered inside stream's `.str-chat__message-status` span, which supplies\n * the flex layout; icon and copy are styled via that span in `styles.css`.\n */\nconst useIsLatestRealMessage = () => {\n const { message } = useMessageContext('SentMessageDeliveryStatus')\n const { messages } = useChannelStateContext('SentMessageDeliveryStatus')\n\n const lastMessageId = (() => {\n if (!messages) {\n return undefined\n }\n\n for (let index = messages.length - 1; index >= 0; index -= 1) {\n const lastMessage = messages[index]\n\n if (\n lastMessage.type === 'system' ||\n lastMessage.type === 'ephemeral' ||\n lastMessage.type === 'deleted'\n ) {\n continue\n }\n\n return lastMessage.id\n }\n\n return undefined\n })()\n\n return message.id === lastMessageId\n}\n\nconst DeliveredStatus = () => {\n if (!useIsLatestRealMessage()) return null\n\n return (\n <>\n <ChecksIcon weight=\"bold\" aria-hidden=\"true\" />\n <span data-testid=\"sent-message-delivery-status\">Delivered</span>\n </>\n )\n}\n\nconst SendingStatus = () => {\n if (!useIsLatestRealMessage()) return null\n\n return (\n <>\n <CircleNotchIcon\n className=\"sent-message-status__spinner animate-spin\"\n weight=\"bold\"\n aria-hidden=\"true\"\n />\n <span data-testid=\"sent-message-sending-status\">Sending</span>\n </>\n )\n}\n\nconst BroadcastStatus = ({\n broadcastId,\n broadcastName,\n resolvedBroadcastName,\n onBroadcastClick,\n}: {\n broadcastId: string\n broadcastName?: string\n resolvedBroadcastName?: string\n onBroadcastClick?: (broadcast: { id: string; name?: string }) => void\n}) => {\n const campaignName = resolvedBroadcastName || broadcastName\n const name = campaignName || 'Broadcast'\n const content = (\n <>\n <span className=\"sent-message-broadcast-badge\" aria-hidden=\"true\">\n <MegaphoneIcon weight=\"fill\" />\n </span>\n <span\n className=\"sent-message-broadcast-name\"\n data-testid=\"sent-message-broadcast-status\"\n >\n {name}\n </span>\n </>\n )\n\n return onBroadcastClick ? (\n <button\n type=\"button\"\n className=\"sent-message-broadcast-link\"\n aria-label={`View broadcast ${name}`}\n onClick={() =>\n onBroadcastClick({\n id: broadcastId,\n ...(campaignName ? { name: campaignName } : {}),\n })\n }\n >\n {content}\n </button>\n ) : (\n content\n )\n}\n\nconst FailedStatus = () => (\n <span className=\"str-chat__message-status sent-message-status--failed\">\n <WarningCircleIcon weight=\"bold\" aria-hidden=\"true\" />\n <span data-testid=\"sent-message-failed-status\">Failed to send</span>\n </span>\n)\n\n/**\n * Delivery status beneath the viewer's most recent sent message (MES-1036):\n * a right-aligned \"✓✓ Delivered\" that appears once Stream confirms the\n * message is stored and updates live.\n *\n * Built on stream-chat-react's `MessageStatus`, which owns the own-message /\n * not-error gating and only surfaces a terminal (sending/sent/delivered/read)\n * state once the message is stored. Stream's `threadList` guard covers\n * sent/delivered/read only — not `sending` — so we suppress the whole stamp\n * (including failed) in thread lists ourselves. Sending uses the same\n * latest-message-in-thread gate; failed messages bypass `MessageStatus`\n * because it has no failed slot and are shown on every own, non-thread\n * failed message.\n */\nexport const SentMessageDeliveryStatus = ({\n onBroadcastClick,\n resolvedBroadcastName,\n}: {\n onBroadcastClick?: (broadcast: { id: string; name?: string }) => void\n resolvedBroadcastName?: string\n}) => {\n const { isMyMessage, message, threadList } = useMessageContext(\n 'SentMessageDeliveryStatus'\n )\n const isOwnMessage = isMyMessage()\n const isLatestRealMessage = useIsLatestRealMessage()\n\n // Stream's MessageStatus still mounts MessageSendingStatus when\n // threadList is true; keep all of our stamps out of thread replies.\n if (threadList) {\n return null\n }\n\n if (message.status === 'failed' && isOwnMessage && message.type !== 'error') {\n return <FailedStatus />\n }\n\n if (!isOwnMessage) {\n return null\n }\n\n const broadcastId = message.metadata?.broadcast_id\n const broadcastName = message.metadata?.broadcast_name\n const broadcastStatus =\n message.status !== 'sending' &&\n message.status !== 'failed' &&\n broadcastId ? (\n <BroadcastStatus\n broadcastId={broadcastId}\n broadcastName={broadcastName}\n resolvedBroadcastName={resolvedBroadcastName}\n onBroadcastClick={onBroadcastClick}\n />\n ) : null\n\n const messageStatus = (\n <MessageStatus\n MessageSentStatus={DeliveredStatus}\n MessageDeliveredStatus={DeliveredStatus}\n MessageReadStatus={DeliveredStatus}\n MessageSendingStatus={SendingStatus}\n />\n )\n\n if (!broadcastStatus) {\n return messageStatus\n }\n\n return (\n <span className=\"sent-message-status-row\">\n {messageStatus}\n {isLatestRealMessage && (\n <span\n className=\"sent-message-broadcast-separator\"\n aria-hidden=\"true\"\n >\n •\n </span>\n )}\n {broadcastStatus}\n </span>\n )\n}\n","/**\n * Scheme detector: `protocol:` per RFC 3986 — a letter followed by any\n * combination of letters, digits, `+`, `.`, or `-` then `:`.\n */\nconst SCHEME_PATTERN = /^([a-z][a-z0-9+.-]*):/i\n\n/**\n * Allowlist of schemes that are safe to forward into `<a href>` for\n * external navigation. `javascript:` / `data:` / `vbscript:` etc. are\n * intentionally **not** on this list — link-attachment data is\n * effectively user-controlled, so passing them through would let a\n * recipient click execute attacker-supplied code or markup.\n */\nconst SAFE_SCHEMES = new Set(['http', 'https', 'mailto', 'tel', 'sms'])\n\n/**\n * Normalize a user-supplied URL into something safe to assign to\n * `<a href>` for external navigation.\n *\n * Link attachments / link apps always point at external destinations\n * (Spotify, TikTok, FAQ links, bare-hostname Linktree URLs like\n * `tr.ee/briemix`, etc.). Without normalization, a bare hostname is\n * treated as a relative path by the browser and clicks navigate within\n * the host site (e.g. `https://linktr.ee/admin/tr.ee/briemix`) instead\n * of opening the intended destination.\n *\n * Rules:\n * - Empty / whitespace-only → returns `undefined` (no href).\n * - Explicit scheme in the safe allowlist (`http`, `https`, `mailto`,\n * `tel`, `sms`) → returned trimmed.\n * - Explicit scheme **not** on the allowlist (`javascript:`, `data:`,\n * `vbscript:`, custom protocols, …) → returns `undefined` so the\n * shell falls back to a non-navigational chrome instead of letting\n * an attacker-controlled URL execute on click.\n * - Protocol-relative (`//example.com/…`) → returned as-is; browsers\n * resolve these against the current page's scheme.\n * - Site-relative path (`/admin/…`) → returned as-is so consumers can\n * still opt into in-app navigation if they really want to.\n * - Bare hostname or anything else → `https://` is prepended so the\n * browser treats it as an external URL.\n */\nexport function normalizeExternalHref(value?: string): string | undefined {\n if (typeof value !== 'string') return undefined\n const trimmed = value.trim()\n if (trimmed === '') return undefined\n\n const schemeMatch = SCHEME_PATTERN.exec(trimmed)\n if (schemeMatch) {\n const scheme = schemeMatch[1].toLowerCase()\n return SAFE_SCHEMES.has(scheme) ? trimmed : undefined\n }\n\n if (trimmed.startsWith('//')) return trimmed\n if (trimmed.startsWith('/')) return trimmed\n return `https://${trimmed}`\n}\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport type { LinkAttachmentCta } from '../../types'\n\nimport type { LinkAttachmentVariant } from './CardShell'\nimport { normalizeExternalHref } from './normalizeExternalHref'\n\nexport interface CardCtaProps {\n variant: LinkAttachmentVariant\n cta: LinkAttachmentCta\n /**\n * Full-width pill (the default — LinkAttachment's single footer button).\n * `false` sizes the button to its content, for an inline multi-action footer\n * row (RichCard's `secondary` footer).\n */\n fullWidth?: boolean\n /**\n * Omit the default `mt-2` top margin when the caller owns spacing — e.g. a\n * multi-action footer that spaces its buttons with its own `gap`. Defaults to\n * `false`, preserving LinkAttachment's button-below-description offset.\n */\n standalone?: boolean\n}\n\ntype CtaEmphasis = NonNullable<LinkAttachmentCta['variant']>\n\n// Two axes: the bubble side (`dark` = sent chrome, `light` = received chrome)\n// crossed with CTA emphasis (`primary` = filled, `secondary` = subtle/grey).\n// `primary` preserves the existing filled button exactly, so callers that omit\n// `cta.variant` are unaffected. Values are explicit rather than DS tokens: the\n// component-library preset has no neutral button token (`primary` is brand\n// purple), and the host preset can't retheme arbitrary hex. Align the grey with\n// the Arbor secondary token when the shared design tokens land.\nconst BUTTON_CLASS_BY_VARIANT: Record<LinkAttachmentVariant, Record<CtaEmphasis, string>> = {\n dark: {\n primary: 'bg-white text-[#121110] hover:bg-white/90',\n secondary: 'bg-white/15 text-white hover:bg-white/25',\n },\n light: {\n primary: 'bg-[#121110] text-white hover:bg-[#2a2928]',\n secondary: 'bg-black/[0.06] text-[#121110] hover:bg-black/10',\n },\n}\n\n/**\n * Pill-shaped CTA rendered below the description on Link App cards that\n * surface an action instead of a URL (e.g. FAQ \"View FAQs\", Form \"Complete form\").\n * Renders as `<a target=\"_blank\">` when `cta.href` is set, otherwise as a\n * plain `<button>`.\n */\nconst CardCta: React.FC<CardCtaProps> = ({\n variant,\n cta,\n fullWidth = true,\n standalone = false,\n}) => {\n const emphasis: CtaEmphasis = cta.variant ?? 'primary'\n const className = classNames(\n 'inline-flex h-10 items-center justify-center rounded-full px-4 text-sm font-medium leading-none transition-colors',\n fullWidth ? 'w-full' : 'min-w-0',\n !standalone && 'mt-2',\n BUTTON_CLASS_BY_VARIANT[variant][emphasis]\n )\n\n // A content-width button (RichCard's `secondary` row, which sits beside the\n // copy in a fixed-width card) can be squeezed by a long or localized label —\n // truncate to an ellipsis instead of overflowing and getting clipped by the\n // card. The full-width button (LinkAttachment's default) spans its own row\n // and never needs this, so it is left exactly as-is.\n const labelNode = fullWidth ? (\n cta.label\n ) : (\n <span className=\"min-w-0 truncate\">{cta.label}</span>\n )\n\n // Mirror the URL normalization used by the shell anchor so bare\n // hostnames (e.g. `tr.ee/foo`) open as external links rather than\n // resolving against the current host.\n const normalizedHref = normalizeExternalHref(cta.href)\n\n if (normalizedHref) {\n return (\n <a\n href={normalizedHref}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={(e) => {\n // Stop the click from bubbling up to the card's anchor wrapper\n // (Received variant) so we don't navigate twice.\n e.stopPropagation()\n cta.onClick?.()\n }}\n className={`${className} no-underline`}\n >\n {labelNode}\n </a>\n )\n }\n\n return (\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation()\n cta.onClick?.()\n }}\n className={className}\n >\n {labelNode}\n </button>\n )\n}\n\nexport default CardCta\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport type { LinkAttachmentCta } from '../../types'\n\nimport CardCta from './CardCta'\nimport type { LinkAttachmentVariant } from './CardShell'\n\nexport interface CardBodyProps {\n variant: LinkAttachmentVariant\n /**\n * Surface the CTA button paints against. Defaults to `variant`; diverges only\n * for the audio strip (a light surface under a nominally `dark` card), so a\n * `secondary` CTA stays legible — matching how `chinTextColor` adapts the chin\n * text. Sourced from `useChinPalette().surface`.\n */\n ctaSurface?: LinkAttachmentVariant\n title?: string\n /** Placeholder shown in the title slot when no title is set (dark variants only). */\n placeholderTitle?: string\n description?: string\n /**\n * Max lines the description renders before truncating with an ellipsis.\n * Defaults to `1` (single-line truncate — the link-preview default). Set\n * higher for cards whose body is prose (e.g. official messages) so it wraps\n * like the mobile card instead of clipping to one line.\n */\n descriptionLines?: number\n /** Footer URL shown below the description. Ignored when `cta` is set. */\n url?: string\n /**\n * Ink for both chin lines, from `useChinPalette` — fixed per surface (white on\n * the dark stop, near-black on the pale stop and the audio strip). Replaces the\n * variant's own `text-*` classes (including their opacity) on both lines, so a\n * card whose surface is not the variant default still reads.\n */\n chinTextColor?: string\n /**\n * Optional 16x16 brand badge rendered before the title (used by Link Apps:\n * Spotify, TikTok, FAQ, Form, etc.).\n */\n appIcon?: React.ReactNode\n /** Optional CTA rendered in place of the URL footer. */\n cta?: LinkAttachmentCta\n /** Trailing action rendered on the right of the title/description block. */\n trailingAction?: React.ReactNode\n /** Renders two neutral pulsing bars in place of the text. */\n isLoading?: boolean\n}\n\nconst TITLE_CLASS_BY_VARIANT: Record<LinkAttachmentVariant, string> = {\n dark: 'text-white',\n light: 'text-black/90',\n}\n\nconst TITLE_DIMMED_CLASS = 'text-white/30'\n\nconst SECONDARY_CLASS_BY_VARIANT: Record<LinkAttachmentVariant, string> = {\n dark: 'text-white/55',\n light: 'text-black/55',\n}\n\n// Skeleton bars, variant-aware for the same reason the placeholder hero is: a\n// fixed dark tint is invisible on the dark card's own dark fill. Values\n// match `SkeletonTextLines`, the toolkit's existing skeleton bars, which also\n// documents why these are explicit black/white rather than `bg-current/N`\n// (Tailwind emits nothing for an opacity modifier on `current`).\nconst SKELETON_BAR_CLASS_BY_VARIANT: Record<LinkAttachmentVariant, string> = {\n dark: 'bg-white/25',\n light: 'bg-black/15',\n}\n\n/**\n * Body of a `LinkAttachment.*` card — the \"chin\". Matches the Figma\n * `content > Labels` group: 16px horizontal padding, 12px vertical padding,\n * 2px gap between the title and the URL. `shrink-0` so it keeps its natural\n * height inside the shell's fixed-height column and the hero absorbs the\n * remainder.\n *\n * Returns `null` when there's nothing to render so plain image / file\n * attachments collapse to a thumbnail-only card — unless the card is still\n * loading, when the skeleton *is* the content.\n */\nconst CardBody: React.FC<CardBodyProps> = ({\n variant,\n ctaSurface,\n chinTextColor,\n title,\n placeholderTitle,\n description,\n descriptionLines = 1,\n url,\n appIcon,\n cta,\n trailingAction,\n isLoading = false,\n}) => {\n const isDark = variant === 'dark'\n const displayTitle = title ?? (isDark ? placeholderTitle : undefined) ?? ''\n const hasTitle = displayTitle.trim() !== ''\n const hasDescription = description != null && description.trim() !== ''\n // Mirror the trimming applied by `ReceivedCard` so a whitespace-only\n // `url` collapses the body footer (and the whole body, for media-only\n // cards) instead of rendering an empty line.\n const trimmedUrl = typeof url === 'string' ? url.trim() : ''\n const hasUrl = trimmedUrl !== ''\n const hasCta = cta != null\n\n if (!isLoading && !hasTitle && !hasDescription && !hasUrl && !hasCta) {\n return null\n }\n\n const titleDimmed = isDark && !title\n\n const titleClass = classNames(\n 'line-clamp-2 text-[14px] font-medium leading-5 tracking-[0.28px]',\n chinTextColor == null &&\n (titleDimmed ? TITLE_DIMMED_CLASS : TITLE_CLASS_BY_VARIANT[variant])\n )\n\n const secondaryClass = classNames(\n 'truncate text-[12px] leading-4 tracking-[0.24px]',\n chinTextColor == null && SECONDARY_CLASS_BY_VARIANT[variant]\n )\n\n // Single-line `truncate` at the default; >1 clamps via `descriptionStyle` below. The URL footer\n // keeps `secondaryClass` so it always stays single-line.\n const descriptionClass = classNames(\n 'text-[12px] leading-4 tracking-[0.24px]',\n descriptionLines <= 1 && 'truncate',\n chinTextColor == null && SECONDARY_CLASS_BY_VARIANT[variant]\n )\n\n if (isLoading) {\n const barClass = classNames(\n 'animate-pulse rounded',\n SKELETON_BAR_CLASS_BY_VARIANT[variant]\n )\n\n return (\n <div className=\"shrink-0 px-4 py-3\">\n <div className=\"flex min-w-0 flex-col gap-[2px]\">\n {/* Sized to the lines they stand in for: 14/20 title, 12/16 URL. */}\n <div className={classNames('h-5 w-3/4', barClass)} aria-hidden />\n <div className={classNames('h-4 w-1/2', barClass)} aria-hidden />\n </div>\n </div>\n )\n }\n\n const chinTextStyle = chinTextColor ? { color: chinTextColor } : undefined\n const descriptionStyle: React.CSSProperties | undefined =\n descriptionLines > 1\n ? {\n ...chinTextStyle,\n display: '-webkit-box',\n WebkitBoxOrient: 'vertical',\n WebkitLineClamp: descriptionLines,\n overflow: 'hidden',\n }\n : chinTextStyle\n\n return (\n <div className=\"shrink-0 px-4 py-3\">\n <div className=\"flex items-end gap-3\">\n <div className=\"flex min-w-0 flex-1 flex-col gap-[2px]\">\n {hasTitle && (\n <div className=\"flex min-w-0 items-center gap-2\">\n {appIcon ? <span className=\"shrink-0\">{appIcon}</span> : null}\n <p\n className={classNames('min-w-0', titleClass)}\n style={chinTextStyle}\n >\n {displayTitle}\n </p>\n </div>\n )}\n\n {hasDescription && (\n <p className={descriptionClass} style={descriptionStyle}>\n {description}\n </p>\n )}\n\n {!hasCta && hasUrl && (\n <p className={secondaryClass} style={chinTextStyle}>\n {trimmedUrl}\n </p>\n )}\n </div>\n\n {trailingAction && <div className=\"shrink-0\">{trailingAction}</div>}\n </div>\n\n {cta && <CardCta variant={ctaSurface ?? variant} cta={cta} />}\n </div>\n )\n}\n\nexport default CardBody\n","import classNames from 'classnames'\nimport React from 'react'\n\nexport type LinkAttachmentVariant = 'dark' | 'light'\n\nexport interface CardShellProps {\n variant: LinkAttachmentVariant\n children: React.ReactNode\n /**\n * When provided, the entire card chrome is rendered as an anchor (used\n * by Sent / Received cards to open the link target on click). Falls\n * back to a `<div>` when omitted so Composer cards stay\n * non-navigational while drafting.\n */\n href?: string\n /**\n * Click handler for the card chrome. When `href` is set the shell is an\n * anchor and `onClick` is invoked in addition to navigation. When `href`\n * is omitted but `onClick` is set, the shell renders as a clickable\n * button (used by media-only Received cards to open an image preview).\n */\n onClick?: () => void\n /** Accessible label for the clickable variant (when `onClick` is set without `href`). */\n ariaLabel?: string\n rootRef?: React.Ref<HTMLElement>\n /**\n * Absolutely-positioned slot rendered in the top-right corner of the\n * shell. Used by the Composer card to surface its dismiss affordance\n * when there's no hero thumbnail to anchor it to.\n */\n topRight?: React.ReactNode\n /**\n * Overrides the variant-derived background colour (e.g. audio cards\n * use `bg-[#F2F3F4]` regardless of the dark/light variant).\n */\n bgClassName?: string\n /**\n * Raw extracted accent colour, published as the `--accent` custom property.\n * `styles.css` derives the card surface from it (`oklch(from …)`) and paints\n * it over the `bg-[…]` fallback where the browser supports it. `undefined`\n * keeps the variant / `bgClassName` fill.\n */\n accentHex?: string\n /**\n * Locks the card to Figma's 250px box and makes it a flex column, so the\n * hero absorbs whatever height the chin leaves. Only the `featured` layout\n * is drawn at a fixed height — `classic` cards and the audio-player card\n * size to their content.\n */\n fixedHeight?: boolean\n /**\n * Renders the shell as a labelled busy region while the link preview is\n * still being fetched. `role=\"status\"` is what makes `aria-label` legal\n * (and announced) on an otherwise generic container.\n */\n busy?: boolean\n 'data-testid'?: string\n}\n\n/**\n * Accessible name for the loading skeleton. Hardcoded English, matching the\n * toolkit's existing convention (`aria-label=\"Dismiss attachment\"`) — there\n * is no i18n layer here.\n */\nexport const LOADING_LABEL = 'Loading link preview'\n\nconst SHELL_CLASS = classNames(\n 'relative w-[290px] select-none overflow-hidden',\n // 20px corners — no token exists for it in the component-library preset,\n // whose radius scale is sm .5rem / md 1rem / lg 1.5rem / xl 4rem.\n 'rounded-[20px]'\n)\n\n// The 290x250 lock: the chin takes the height its 1-2 line title needs\n// (82px at two lines, 62px at one) and the hero absorbs the remainder.\nconst FIXED_HEIGHT_CLASS = 'flex h-[250px] flex-col'\n\n/**\n * Outer chrome for every `LinkAttachment.*` card. Matches the 290px x 250px\n * box, 20px corner radius, and borderless / shadowless treatment of the\n * composer and sent instances in Figma.\n */\nconst CardShell: React.FC<CardShellProps> = ({\n variant,\n children,\n href,\n onClick,\n ariaLabel,\n rootRef,\n topRight,\n bgClassName,\n accentHex,\n fixedHeight = false,\n busy = false,\n 'data-testid': dataTestId,\n}) => {\n const isInteractive = href != null || onClick != null\n const className = classNames(\n 'messaging-link-card',\n SHELL_CLASS,\n fixedHeight ? FIXED_HEIGHT_CLASS : 'block',\n // The plain fallback fill, kept underneath the `--accent` derivation: the\n // `.messaging-link-card[style*='--accent']` rule in `styles.css` outranks\n // this single class, so a published accent paints over it and an unset one\n // lands on this surface.\n //\n // Must be an arbitrary value, not a theme token: this package ships no\n // Tailwind utilities, so consumers generate our classes by scanning\n // `messaging-react/dist` against their own theme. A theme colour a consumer\n // lacks compiles to nothing and leaves the chin transparent; an arbitrary\n // value is generated from the class name alone.\n //\n // Both values are the plain message bubble's own fill, so a card with no\n // usable accent matches the text bubbles around it: `#1e2330` is\n // `--str-chat__own-message-bubble-background-color` and `#f1f0ee` is\n // `--str-chat__message-bubble-background-color`, mirrored by the bubble\n // tail's own fills in `styles.css`.\n bgClassName ?? (variant === 'dark' ? 'bg-[#1e2330]' : 'bg-[#f1f0ee]'),\n // `focus-ring` is a design-system utility from the component-library\n // tailwind preset — outline-none + a black 2px focus-visible ring\n // with offset, so keyboard users can see the focused card.\n isInteractive ? 'cursor-pointer no-underline focus-ring' : null\n )\n\n const busyProps = busy\n ? { role: 'status', 'aria-busy': true, 'aria-label': LOADING_LABEL }\n : undefined\n\n // Published as a custom property, not a `background`: the `[style*='--accent']`\n // rule in `styles.css` keys off its presence and derives the surface in CSS.\n const accentStyle = accentHex\n ? ({ '--accent': accentHex } as React.CSSProperties)\n : undefined\n\n const corner = topRight ? (\n <div className=\"pointer-events-auto absolute right-3 top-3 z-10\">\n {topRight}\n </div>\n ) : null\n\n if (href) {\n return (\n <a\n ref={rootRef as React.Ref<HTMLAnchorElement>}\n href={href}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n onClick={onClick}\n data-testid={dataTestId}\n data-variant={variant}\n className={className}\n style={accentStyle}\n {...busyProps}\n >\n {children}\n {corner}\n </a>\n )\n }\n\n if (onClick) {\n return (\n <button\n ref={rootRef as React.Ref<HTMLButtonElement>}\n type=\"button\"\n onClick={onClick}\n aria-label={ariaLabel}\n data-testid={dataTestId}\n data-variant={variant}\n className={classNames(className, 'text-left')}\n style={accentStyle}\n {...busyProps}\n >\n {children}\n {corner}\n </button>\n )\n }\n\n return (\n <div\n ref={rootRef as React.Ref<HTMLDivElement>}\n data-testid={dataTestId}\n data-variant={variant}\n className={className}\n style={accentStyle}\n {...busyProps}\n >\n {children}\n {corner}\n </div>\n )\n}\n\nexport default CardShell\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport { optimizeMessagingAttachmentUrl } from '../../../../utils/cdnImageUrl'\nimport { renderTypeIcon } from '../../../AttachmentCard'\nimport { getSourceType } from '../../../AttachmentCard/utils/mimeType'\n\nimport type { LinkAttachmentVariant } from './CardShell'\n\nexport interface CardThumbnailProps {\n variant: LinkAttachmentVariant\n /** Source URL of the hero image (or poster for video). */\n thumbnailUrl?: string\n /**\n * Playable media URL. When provided alongside a video / audio `mimeType`,\n * the hero region renders a native HTML5 player with controls instead of\n * the static thumbnail / placeholder.\n */\n sourceUrl?: string\n /** Alt text — typically the card's title. */\n title?: string\n /**\n * Drives the placeholder type icon when no `thumbnailUrl` is provided,\n * and selects between image / video / audio rendering when `sourceUrl`\n * is set. Defaults to a generic image icon when unset.\n */\n mimeType?: string\n /** Optional decorations layered into the top corners of the thumbnail. */\n topLeft?: React.ReactNode\n topRight?: React.ReactNode\n /**\n * Renders a neutral pulsing block instead of the image while the link\n * preview is still being fetched.\n */\n isLoading?: boolean\n /**\n * Fired when the hero image fails to load. The owning card clears\n * `thumbnailUrl` (so the type-icon placeholder takes over) and drops the\n * accent colour, which was derived from an image the viewer cannot see.\n */\n onImageError?: () => void\n}\n\nconst PLACEHOLDER_BG: Record<LinkAttachmentVariant, string> = {\n dark: 'bg-white/10',\n light: 'bg-black/5',\n}\n\nconst PLACEHOLDER_ICON: Record<LinkAttachmentVariant, string> = {\n dark: 'size-16 text-white/25',\n light: 'size-16 text-black/25',\n}\n\n/** Mime + sourceUrl gives us a playable audio attachment. */\nexport const isPlayableAudio = (mimeType?: string, sourceUrl?: string) =>\n !!sourceUrl && !!mimeType && getSourceType(mimeType) === 'audio'\n\n/**\n * Mime + sourceUrl gives us a playable video or audio attachment. Used by\n * Received to skip wrapping the shell in an interactive `<button>` so the\n * native media controls remain operable.\n */\nexport const isPlayableMedia = (mimeType?: string, sourceUrl?: string) => {\n if (!sourceUrl || !mimeType) return false\n const source = getSourceType(mimeType)\n return source === 'video' || source === 'audio'\n}\n\n/**\n * Background colour the LinkAttachment cards switch to when the source is\n * audio — flat neutral around the native `<audio>` chrome regardless of\n * the dark / light variant.\n *\n * The hex is exported alongside the class because the card's ink is chosen by\n * measuring contrast against whatever surface it lands on, and contrast needs\n * channels rather than a class name. They are written out twice on purpose:\n * Tailwind only generates utilities it can find as complete literals when it\n * scans `dist`, so building the class from the constant\n * (`` `bg-[${AUDIO_BG}]` ``) would emit nothing at all. `chinContrast.test.ts`\n * asserts the two stay in step.\n */\nexport const AUDIO_BG = '#F2F3F4'\nexport const AUDIO_BG_CLASS = 'bg-[#F2F3F4]'\n\n/**\n * Hero region shown above the card body. Inside the `featured` card's fixed\n * 250px box it takes `flex-1`, absorbing whatever height the chin leaves.\n * Renders, in priority order: a loading placeholder, a native\n * `<video controls>` / `<audio controls>` when `sourceUrl` + mime say so, the\n * thumbnail image, then a type-icon placeholder.\n */\nconst CardThumbnail: React.FC<CardThumbnailProps> = ({\n variant,\n thumbnailUrl,\n sourceUrl,\n title,\n mimeType = 'image/*',\n topLeft,\n topRight,\n isLoading = false,\n onImageError,\n}) => {\n const sourceType = getSourceType(mimeType)\n const isPlayableVideo = !!sourceUrl && sourceType === 'video'\n const optimizedThumbnailUrl = optimizeMessagingAttachmentUrl(thumbnailUrl)\n\n // `!isLoading` first, deliberately: while the scrape is in flight we do not\n // yet know what the asset is, so honouring a `mimeType` the caller happens to\n // have set already would mount a live player next to `CardBody`'s skeleton\n // bars. The loading branch below owns every hero shape (MES-1349).\n if (!isLoading && isPlayableAudio(mimeType, sourceUrl)) {\n // Audio collapses the hero entirely — the native player sits inside\n // the card chrome with a bit of padding so the card background\n // (typically `bg-[#F2F3F4]`) is visible around it.\n return (\n <div className=\"p-3\">\n <audio\n src={sourceUrl}\n controls\n preload=\"metadata\"\n className=\"block w-full\"\n >\n <track kind=\"captions\" />\n </audio>\n </div>\n )\n }\n\n return (\n <div\n className={classNames(\n // `flex-1 min-h-0` inside the shell's 250px column: the hero takes\n // every pixel the chin doesn't, which is 168px at a two-line title\n // and 188px at one.\n 'relative min-h-0 w-full flex-1 overflow-hidden',\n isPlayableVideo && 'bg-black'\n )}\n >\n {isLoading ? (\n // Reuses the placeholder fill: a fixed `bg-black/[0.06]` would be\n // invisible on the dark card's dark surface. Stock Tailwind only\n // — `animate-shimmer` exists in this package's own tailwind config but\n // not in either consumer's, so it would compile to nothing in\n // frontyard and profiles while looking right in this Storybook.\n <div\n className={classNames(\n 'h-full w-full animate-pulse',\n PLACEHOLDER_BG[variant]\n )}\n aria-hidden\n />\n ) : isPlayableVideo ? (\n <>\n <video\n src={sourceUrl}\n poster={optimizedThumbnailUrl}\n controls\n playsInline\n preload=\"metadata\"\n className=\"absolute inset-0 h-full w-full object-contain\"\n >\n <track kind=\"captions\" />\n </video>\n {/*\n The poster *is* the hero on this branch, and `<video>` raises no\n event when one fails to load — its own `error` is about the media\n resource, not the poster — so the `<img>` below verifies it. A\n `display:none` image is still fetched and still fires `onError`\n (checked in a browser, not assumed), and it dedupes with the\n poster's own request for the same URL, so this costs a DOM node\n rather than a round trip. No `loading=\"lazy\"`: that would defer the\n probe while off screen. Without it a 404 poster left the card and\n its tail accent-painted over a bare `bg-black` box, which is the\n one thing `useChinPalette`'s rule exists to prevent (MES-1349).\n */}\n {optimizedThumbnailUrl && onImageError ? (\n <img\n src={optimizedThumbnailUrl}\n alt=\"\"\n aria-hidden\n onError={onImageError}\n className=\"hidden\"\n />\n ) : null}\n </>\n ) : thumbnailUrl ? (\n <img\n src={optimizedThumbnailUrl ?? thumbnailUrl}\n alt={title ?? ''}\n draggable={false}\n loading=\"lazy\"\n decoding=\"async\"\n onError={onImageError}\n className=\"absolute inset-0 h-full w-full object-cover\"\n />\n ) : (\n <div\n className={classNames(\n 'flex h-full w-full items-center justify-center',\n PLACEHOLDER_BG[variant]\n )}\n >\n {renderTypeIcon(mimeType, {\n className: PLACEHOLDER_ICON[variant],\n weight: 'regular',\n })}\n </div>\n )}\n\n {topLeft ? (\n <div className=\"pointer-events-auto absolute left-3 top-3 z-10\">\n {topLeft}\n </div>\n ) : null}\n {topRight ? (\n <div className=\"pointer-events-auto absolute right-3 top-3 z-10\">\n {topRight}\n </div>\n ) : null}\n </div>\n )\n}\n\nexport default CardThumbnail\n","/**\n * The hex contract for the accent colour `useChinPalette` publishes.\n *\n * The extracted `accent_color` (from `getDominantColorFromImageUrl` via a Stream\n * attachment field) is always a hex, but CSS `oklch(from …)` would resolve any\n * colour name — including `rebeccapurple` — so the input is narrowed to a hex\n * before it is published as `--accent`, rather than widening that contract by\n * accident.\n */\n\n/**\n * `#abc`, `#aabbcc`, or either without the leading `#`.\n *\n * The optional hash is deliberate: the extractor returns bare hexes often\n * enough that rejecting them would drop the colour. It is also why `toCssHex`\n * exists — `738883` without the `#` is not a valid CSS colour.\n */\nexport const HEX_PATTERN = /^#?(?:[0-9a-f]{3}|[0-9a-f]{6})$/i\n\n/** Normalizes a hex for CSS, adding the `#` the extractor omits. */\nexport function toCssHex(hex: string): string {\n const trimmed = hex.trim()\n return trimmed.startsWith('#') ? trimmed : `#${trimmed}`\n}\n","/* eslint-disable react-hooks/set-state-in-effect -- pre-existing pattern flagged by the React\n Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is\n out of scope for the lint toolchain upgrade. */\nimport React from 'react'\n\nimport type { LinkAttachmentLayout, LinkAttachmentStatus } from '../../types'\n\nimport type { LinkAttachmentVariant } from './CardShell'\nimport { isPlayableAudio } from './CardThumbnail'\nimport { HEX_PATTERN, toCssHex } from './hex'\n\n/** White on the dark stop, near-black on the light stop and the audio strip. */\nconst INK_ON_DARK = '#FFFFFF'\n/** The near-black used across the Figma frames. */\nconst INK_ON_LIGHT = '#040403'\n\nconst INK_BY_VARIANT: Record<LinkAttachmentVariant, string> = {\n dark: INK_ON_DARK,\n light: INK_ON_LIGHT,\n}\n\nexport interface ChinPaletteInput {\n variant: LinkAttachmentVariant\n accentColor?: string\n status?: LinkAttachmentStatus\n layout?: LinkAttachmentLayout\n thumbnailUrl?: string\n /**\n * Together with `sourceUrl`, identifies the playable-audio card whose hero\n * region is the native player rather than an image. See `hasHero` below.\n */\n mimeType?: string\n sourceUrl?: string\n}\n\nexport interface ChinPalette {\n /**\n * Hero URL to render. Cleared once the image has errored so `CardThumbnail`\n * falls through to its type-icon placeholder instead of a broken `<img>`.\n */\n thumbnailUrl?: string\n /**\n * Raw extracted colour to publish as the `--accent` custom property on the\n * shell and the tail, or `undefined` when there is no paintable accent. The\n * card keeps its hue and swaps the lightness in CSS (`styles.css`).\n */\n accentHex?: string\n /**\n * Ink for both chin lines: white on the sender's dark stop, near-black on the\n * receiver's pale stop and on the audio strip. Fixed per surface — the stops\n * clear AA at every hue.\n */\n chinTextColor: string\n /**\n * The surface the card actually paints against, for callers whose colours must\n * match it (the CTA button). Equals `variant` except on the audio strip, whose\n * hero is the light `AUDIO_BG_CLASS` regardless of side — so a `dark` audio card\n * paints a light surface and the CTA must follow it, the way `chinTextColor`\n * already does for the chin text.\n */\n surface: LinkAttachmentVariant\n /** True while the card renders as a skeleton. */\n isLoading: boolean\n /** Hand to `CardThumbnail` so a broken hero drops the accent with it. */\n onImageError: () => void\n}\n\n/**\n * Resolves the accent a link card publishes and tracks hero-image failure.\n *\n * The accent is a claim about an image the viewer can see, so it only paints\n * when `CardThumbnail` actually mounts that image. Mirroring its branches, the\n * hero is *not* the accent's source when:\n *\n * - `layout: 'classic'` — there is no hero region at all;\n * - `status` is not `'ready'` — the hero is the pulsing skeleton, no image yet;\n * - `thumbnailUrl` is absent — the hero is the type-icon placeholder, or for a\n * playable video a bare `bg-black` box with no poster;\n * - the attachment is playable audio — the hero collapses to the native\n * `<audio>` player and the `<img>` never mounts even though `thumbnailUrl` is\n * set, so `onImageError` can never rescue it and the gate has to be up front;\n * - the image 404s — an accent extracted from something the viewer cannot see.\n * `onImageError` covers both hero shapes: the `<img>` reports directly, and a\n * playable video's `poster` (which raises no event of its own) is verified by\n * the hidden probe image in `CardThumbnail`.\n *\n * Every excluded case publishes no accent and keeps the variant's own fill.\n */\nexport function useChinPalette({\n variant,\n accentColor,\n status = 'ready',\n layout = 'featured',\n thumbnailUrl,\n mimeType,\n sourceUrl,\n}: ChinPaletteInput): ChinPalette {\n const [heroFailed, setHeroFailed] = React.useState(false)\n\n // A new hero URL is a new chance to load — otherwise a card that recycles\n // (same element, different attachment) would stay stuck on the placeholder.\n React.useEffect(() => {\n setHeroFailed(false)\n }, [thumbnailUrl])\n\n const onImageError = React.useCallback(() => {\n setHeroFailed(true)\n }, [])\n\n // Truthiness rather than `!= null` so this tracks `CardThumbnail`'s own\n // `thumbnailUrl ?` branch, which renders the placeholder for `''` too.\n //\n // Playable audio stays excluded: its hero collapses to the native `<audio>`\n // player, so the artwork never appears and an accent taken from it would claim\n // an image the viewer cannot see. The `<img>` never mounts either, so\n // `onImageError` could not withdraw the colour if the artwork 404'd.\n const isAudio = isPlayableAudio(mimeType, sourceUrl)\n const hasHero =\n layout === 'featured' && !!thumbnailUrl && !isAudio && !heroFailed\n const canDerive = hasHero && status === 'ready'\n // Only a validated hex is published: the extracted `accent_color` is always a\n // hex, and CSS would resolve any colour name, so narrow the input here. Bare\n // hexes are accepted because the extractor omits the `#` often enough that\n // rejecting them would silently lose the colour.\n const accentHex =\n canDerive && accentColor != null && HEX_PATTERN.test(accentColor.trim())\n ? toCssHex(accentColor)\n : undefined\n\n // The accent surface and the sender's dark fallback both take white ink; the\n // pale receiver fallback and the imposed audio strip both take near-black — so\n // the ink is fixed per surface.\n const chinTextColor = isAudio ? INK_ON_LIGHT : INK_BY_VARIANT[variant]\n const surface: LinkAttachmentVariant = isAudio ? 'light' : variant\n\n return {\n thumbnailUrl: heroFailed ? undefined : thumbnailUrl,\n accentHex,\n chinTextColor,\n surface,\n isLoading: status === 'loading',\n onImageError,\n }\n}\n","import { PencilSimpleIcon, XIcon } from '@phosphor-icons/react'\nimport classNames from 'classnames'\nimport React from 'react'\n\nimport type { LinkAttachmentBaseProps } from '../../types'\nimport CardBody from '../_shared/CardBody'\nimport CardShell from '../_shared/CardShell'\nimport CardThumbnail, {\n AUDIO_BG_CLASS,\n isPlayableAudio,\n} from '../_shared/CardThumbnail'\nimport { useChinPalette } from '../_shared/useChinPalette'\n\nexport interface ComposerCardProps extends LinkAttachmentBaseProps {\n /**\n * When provided, renders a dismiss X in the thumbnail corner. Called when\n * the composer clicks it to remove the attachment.\n */\n onDismiss?: () => void\n /**\n * When provided, renders a pencil button to the right of the description\n * that the composer can use to edit the attachment metadata.\n */\n onEditClick?: () => void\n /**\n * Accessible name for the dismiss button. Defaults to English because the\n * toolkit has no i18n layer; consumers that do (frontyard passes\n * `ComposerLinkPreviewList.closeLinkPreview`) should supply their own.\n */\n dismissLabel?: string\n}\n\n// 24x24 circle with a 12x12 white X — already Figma-exact.\nconst DISMISS_CLASS =\n 'flex size-6 items-center justify-center rounded-full bg-[#121110] text-white focus-ring'\n\n// Grows only the *clickable* area to 44x44 (24 + 10 + 10) for the platform\n// touch minimum (MES-1350 / MES-1358), leaving the visual box alone — this\n// same card renders in profiles' touch-first visitor composer, so a\n// consumer-scoped fix could not reach it. Same idiom as the send button in\n// `CustomMessageInput/index.tsx`. Not applied on the audio variant, where the\n// button is an inline sibling of the native controls rather than an overlay\n// and the halo would sit on top of them.\nconst DISMISS_TOUCH_TARGET_CLASS =\n \"relative before:absolute before:-inset-[10px] before:content-['']\"\n\n/**\n * The card the composer sees while drafting a link attachment.\n * Matches the Composer column of the messaging design system in Figma.\n */\nconst ComposerCard: React.FC<ComposerCardProps> = ({\n title,\n placeholderTitle,\n description,\n descriptionLines,\n url,\n mimeType,\n thumbnailUrl,\n sourceUrl,\n layout = 'featured',\n appIcon,\n cta,\n accentColor,\n status = 'ready',\n onDismiss,\n onEditClick,\n dismissLabel = 'Dismiss attachment',\n}) => {\n const isClassic = layout === 'classic'\n const isAudio = isPlayableAudio(mimeType, sourceUrl)\n const chin = useChinPalette({\n variant: 'dark',\n accentColor,\n status,\n layout,\n thumbnailUrl,\n mimeType,\n sourceUrl,\n })\n\n const dismissButton = onDismiss ? (\n <button\n type=\"button\"\n onClick={onDismiss}\n aria-label={dismissLabel}\n className={classNames(\n DISMISS_CLASS,\n !isAudio && DISMISS_TOUCH_TARGET_CLASS\n )}\n >\n <XIcon className=\"size-3\" weight=\"bold\" />\n </button>\n ) : undefined\n\n const editButton = onEditClick ? (\n <button\n type=\"button\"\n onClick={onEditClick}\n aria-label=\"Edit attachment\"\n className=\"flex size-10 items-center justify-center rounded-full bg-white/10 text-white hover:bg-white/15\"\n >\n <PencilSimpleIcon className=\"size-5\" weight=\"regular\" />\n </button>\n ) : undefined\n\n // Audio cards collapse to just the native player — render the dismiss\n // button as an inline sibling so it always has its own space and never\n // overlaps the audio control's volume/menu buttons. Skipped while loading:\n // this path has no `CardBody` and no `busy`, so taking it during a scrape\n // would drop the skeleton and its `aria-busy` region entirely, and we do not\n // yet know the asset is audio anyway.\n if (isAudio && !chin.isLoading) {\n return (\n <CardShell variant=\"dark\" bgClassName={AUDIO_BG_CLASS}>\n <div className=\"flex items-center gap-2 pr-3\">\n <div className=\"min-w-0 flex-1\">\n <CardThumbnail\n variant=\"dark\"\n sourceUrl={sourceUrl}\n title={title}\n mimeType={mimeType}\n />\n </div>\n {dismissButton && <div className=\"shrink-0\">{dismissButton}</div>}\n </div>\n </CardShell>\n )\n }\n\n return (\n <CardShell\n variant=\"dark\"\n fixedHeight={!isClassic}\n accentHex={chin.accentHex}\n busy={chin.isLoading}\n topRight={isClassic ? dismissButton : undefined}\n >\n {!isClassic && (\n <CardThumbnail\n variant=\"dark\"\n thumbnailUrl={chin.thumbnailUrl}\n sourceUrl={sourceUrl}\n title={title}\n mimeType={mimeType}\n topRight={dismissButton}\n isLoading={chin.isLoading}\n onImageError={chin.onImageError}\n />\n )}\n <CardBody\n chinTextColor={chin.chinTextColor}\n variant=\"dark\"\n ctaSurface={chin.surface}\n title={title}\n placeholderTitle={placeholderTitle}\n description={description}\n descriptionLines={descriptionLines}\n url={url}\n appIcon={appIcon}\n cta={cta}\n trailingAction={editButton}\n isLoading={chin.isLoading}\n />\n </CardShell>\n )\n}\n\nexport default ComposerCard\n","import classNames from 'classnames'\nimport React from 'react'\n\nexport interface BubbleTailProps {\n /**\n * Raw extracted accent colour, published as `--accent` so the tail derives\n * the same surface as the card in CSS (`styles.css`), or `undefined` to fall\n * back to this side's plain bubble fill. The tail paints outside the shell's\n * box and can't inherit its background, so it runs the derivation off its own\n * `--accent`.\n */\n accentHex?: string\n /** Sender tails hang off the right edge, receiver tails mirror to the left. */\n side: 'sender' | 'receiver'\n /**\n * `false` renders `children` untouched — a card mid-run has no tail because\n * a later bubble in the run owns it.\n */\n visible: boolean\n children: React.ReactNode\n}\n\n/**\n * Wraps a `LinkAttachment` card in the chat bubble tail from the messaging\n * design system, painted to match the card's own bottom edge.\n *\n * The geometry and mask live in `styles.css` alongside the text-bubble tails\n * they mirror. They cannot be reused directly: those rules key off\n * `.str-chat__message-bubble:last-child`, which only exists when the card is\n * rendered inside a Stream thread — and `StreamAttachmentMessage` marks that\n * bubble `data-attachment-bubble` precisely to opt out of them, since a tail\n * tinted from the text-bubble surface would clash with a coloured card.\n */\nconst BubbleTail: React.FC<BubbleTailProps> = ({\n accentHex,\n side,\n visible,\n children,\n}) => {\n if (!visible) return <>{children}</>\n\n // Sender tails sit on the dark stop, receiver tails on the pale one — the\n // same mapping the cards use (`SentCard` is `dark`, `ReceivedCard` `light`).\n const variant = side === 'sender' ? 'dark' : 'light'\n const accentStyle = accentHex\n ? ({ '--accent': accentHex } as React.CSSProperties)\n : undefined\n\n return (\n // `isolate` keeps the tail's negative z-index from escaping behind the\n // thread background; `w-fit` stops the wrapper from stretching in the\n // composer's flex list.\n <div className=\"relative isolate w-fit\">\n {children}\n <span\n aria-hidden\n data-testid=\"link-card-tail\"\n data-variant={variant}\n style={accentStyle}\n className={classNames(\n 'messaging-link-card-tail',\n side === 'receiver' && 'messaging-link-card-tail--received'\n )}\n />\n </div>\n )\n}\n\nexport default BubbleTail\n","import React from 'react'\n\nimport type { BubbleGroupPosition } from '../../../MessageAttachment/types'\nimport type { LinkAttachmentBaseProps } from '../../types'\nimport BubbleTail from '../_shared/BubbleTail'\nimport CardBody from '../_shared/CardBody'\nimport CardShell from '../_shared/CardShell'\nimport CardThumbnail, {\n AUDIO_BG_CLASS,\n isPlayableAudio,\n isPlayableMedia,\n} from '../_shared/CardThumbnail'\nimport { normalizeExternalHref } from '../_shared/normalizeExternalHref'\nimport { useChinPalette } from '../_shared/useChinPalette'\n\nexport interface ReceivedCardProps extends LinkAttachmentBaseProps {\n /**\n * Fired when the recipient activates the card. Behavior depends on how\n * the card is configured:\n * - **Link app with a CTA** (FAQ / Form): the CTA owns navigation;\n * `onClick` fires when the recipient taps the CTA itself, alongside\n * `cta.onClick` (use for analytics).\n * - **Link app with a URL** (Spotify / TikTok / generic link): the card\n * chrome is an `<a target=\"_blank\">` opening `url` — `onClick` fires\n * alongside the navigation (use for analytics).\n * - **Hero-image only card**: the card has no URL, so it renders as\n * a button. `onClick` is the consumer's hook for opening a preview.\n * - **Video / audio link previews**: the shell stays non-interactive\n * so the native media controls remain operable — `onClick` is\n * ignored in this configuration.\n */\n onClick?: () => void\n /**\n * Position of this card inside its same-author run. `'single'` / `'end'`\n * paint the accent-coloured tail, mirrored to the card's left edge;\n * `'first'` / `'middle'` omit it because a later bubble in the run owns it.\n * Defaults to `'single'`.\n */\n groupPosition?: BubbleGroupPosition\n}\n\n/**\n * The card the recipient sees in chat for a link attachment. Matches the\n * Received column of the messaging design system in Figma.\n */\nconst ReceivedCard: React.FC<ReceivedCardProps> = ({\n title,\n description,\n descriptionLines,\n url,\n mimeType,\n thumbnailUrl,\n sourceUrl,\n layout = 'featured',\n appIcon,\n cta,\n accentColor,\n status = 'ready',\n onClick,\n groupPosition = 'single',\n}) => {\n // Video / audio link previews wrap the native media element — render a\n // plain non-interactive shell and let the media controls own clicks so\n // taps on play/pause/scrubber don't fire the outer card action.\n const isPlayingMedia = isPlayableMedia(mimeType, sourceUrl)\n // Normalize the URL so a bare hostname like `tr.ee/briemix` (used in\n // our own docs / stories) is treated as an external link instead of a\n // relative path. Returns `undefined` for empty / whitespace-only\n // values, so those fall through to the preview path instead of\n // producing an empty `href` on the shell anchor.\n const normalizedUrl = normalizeExternalHref(url)\n const shellHref =\n cta == null && normalizedUrl != null && !isPlayingMedia\n ? normalizedUrl\n : undefined\n const shellOnClick = cta == null && !isPlayingMedia ? onClick : undefined\n // A loading card is not yet an audio card: the scrape that would tell us has\n // not returned, so it keeps the ordinary 250px skeleton rather than the\n // intrinsic-height audio shell (whose `flex-1` hero would collapse to 0).\n const audioBg =\n status !== 'loading' && isPlayableAudio(mimeType, sourceUrl)\n ? AUDIO_BG_CLASS\n : undefined\n\n // When a CTA is set the shell isn't interactive — the CTA owns the\n // click target. Forward the card-level `onClick` to the CTA so\n // analytics / side-effect consumers still fire on activation while\n // preserving the CTA's own `onClick` callback.\n const wrappedCta =\n cta && onClick\n ? {\n ...cta,\n onClick: () => {\n onClick()\n cta.onClick?.()\n },\n }\n : cta\n\n const chin = useChinPalette({\n variant: 'light',\n accentColor,\n status,\n layout,\n thumbnailUrl,\n mimeType,\n sourceUrl,\n })\n\n return (\n <BubbleTail\n accentHex={chin.accentHex}\n side=\"receiver\"\n // Only the last card in a same-author run carries the tail; the audio\n // player card is not a bubble, so it never does.\n visible={\n audioBg == null &&\n (groupPosition === 'single' || groupPosition === 'end')\n }\n >\n <CardShell\n variant=\"light\"\n href={shellHref}\n onClick={shellOnClick}\n ariaLabel={title ?? 'Open attachment preview'}\n fixedHeight={layout === 'featured' && audioBg == null}\n accentHex={chin.accentHex}\n busy={chin.isLoading}\n bgClassName={audioBg}\n data-testid=\"link-attachment\"\n >\n {layout === 'featured' && (\n <CardThumbnail\n variant=\"light\"\n thumbnailUrl={chin.thumbnailUrl}\n sourceUrl={sourceUrl}\n title={title}\n mimeType={mimeType}\n isLoading={chin.isLoading}\n onImageError={chin.onImageError}\n />\n )}\n <CardBody\n chinTextColor={chin.chinTextColor}\n variant=\"light\"\n ctaSurface={chin.surface}\n title={title}\n description={description}\n descriptionLines={descriptionLines}\n url={url}\n appIcon={appIcon}\n cta={wrappedCta}\n isLoading={chin.isLoading}\n />\n </CardShell>\n </BubbleTail>\n )\n}\n\nexport default ReceivedCard\n","import React from 'react'\n\nimport type { BubbleGroupPosition } from '../../../MessageAttachment/types'\nimport type { LinkAttachmentBaseProps } from '../../types'\nimport BubbleTail from '../_shared/BubbleTail'\nimport CardBody from '../_shared/CardBody'\nimport CardShell from '../_shared/CardShell'\nimport CardThumbnail, {\n AUDIO_BG_CLASS,\n isPlayableAudio,\n isPlayableMedia,\n} from '../_shared/CardThumbnail'\nimport { normalizeExternalHref } from '../_shared/normalizeExternalHref'\nimport { useChinPalette } from '../_shared/useChinPalette'\n\nexport interface SentCardProps extends LinkAttachmentBaseProps {\n /**\n * Fired when the sender activates their own posted card. Mirrors the\n * `Received` variant so analytics fires symmetrically on both sides\n * of the conversation. Behavior depends on configuration:\n * - **Link app with a URL**: the card chrome is an `<a target=\"_blank\">`\n * opening `url`; `onClick` fires alongside the navigation.\n * - **Link app with a CTA**: the CTA owns navigation; `onClick` is\n * ignored (the CTA's own `onClick` is the analytics hook).\n * - **Video / audio link previews**: the shell stays non-interactive\n * so the native media controls remain operable; `onClick` is ignored.\n */\n onClick?: () => void\n /**\n * Position of this card inside its same-author run. `'single'` / `'end'`\n * paint the accent-coloured tail; `'first'` / `'middle'` omit it because a\n * later bubble in the run owns it. Defaults to `'single'`.\n */\n groupPosition?: BubbleGroupPosition\n}\n\n/**\n * The card the sender sees in chat after a link attachment has been posted.\n * Matches the Sent column of the messaging design system in Figma — same\n * dark chrome as the Composer card minus the dismiss / edit affordances.\n *\n * The sender's own copy is clickable when a `url` is set so they can open\n * the link they just sent — symmetric with `LinkAttachment.Received`.\n */\nconst SentCard: React.FC<SentCardProps> = ({\n title,\n placeholderTitle,\n description,\n descriptionLines,\n url,\n mimeType,\n thumbnailUrl,\n sourceUrl,\n layout = 'featured',\n appIcon,\n cta,\n accentColor,\n status = 'ready',\n onClick,\n groupPosition = 'single',\n}) => {\n // Video / audio link previews wrap the native media element — render a\n // plain non-interactive shell and let the media controls own clicks so\n // taps on play/pause/scrubber don't fire the outer card action. Same\n // rationale as `ReceivedCard`.\n const isPlayingMedia = isPlayableMedia(mimeType, sourceUrl)\n // Normalize the URL so a bare hostname like `tr.ee/briemix` is treated\n // as an external link instead of resolving against the current host.\n // Returns `undefined` for empty / whitespace-only values, so those fall\n // through to a non-navigational shell instead of producing an empty\n // `href` on the anchor.\n const normalizedUrl = normalizeExternalHref(url)\n const shellHref =\n cta == null && normalizedUrl != null && !isPlayingMedia\n ? normalizedUrl\n : undefined\n const shellOnClick =\n cta == null && !isPlayingMedia && shellHref != null ? onClick : undefined\n\n // A loading card is not yet an audio card: the scrape that would tell us has\n // not returned, so it keeps the ordinary 250px skeleton rather than the\n // intrinsic-height audio shell (whose `flex-1` hero would collapse to 0).\n const isAudio = status !== 'loading' && isPlayableAudio(mimeType, sourceUrl)\n const chin = useChinPalette({\n variant: 'dark',\n accentColor,\n status,\n layout,\n thumbnailUrl,\n mimeType,\n sourceUrl,\n })\n\n return (\n <BubbleTail\n accentHex={chin.accentHex}\n side=\"sender\"\n // Only the last card in a same-author run carries the tail; the audio\n // player card is not a bubble, so it never does.\n visible={\n !isAudio && (groupPosition === 'single' || groupPosition === 'end')\n }\n >\n <CardShell\n variant=\"dark\"\n href={shellHref}\n onClick={shellOnClick}\n fixedHeight={layout === 'featured' && !isAudio}\n accentHex={chin.accentHex}\n busy={chin.isLoading}\n bgClassName={isAudio ? AUDIO_BG_CLASS : undefined}\n >\n {layout === 'featured' && (\n <CardThumbnail\n variant=\"dark\"\n thumbnailUrl={chin.thumbnailUrl}\n sourceUrl={sourceUrl}\n title={title}\n mimeType={mimeType}\n isLoading={chin.isLoading}\n onImageError={chin.onImageError}\n />\n )}\n <CardBody\n chinTextColor={chin.chinTextColor}\n variant=\"dark\"\n ctaSurface={chin.surface}\n title={title}\n placeholderTitle={placeholderTitle}\n description={description}\n descriptionLines={descriptionLines}\n url={url}\n appIcon={appIcon}\n cta={cta}\n isLoading={chin.isLoading}\n />\n </CardShell>\n </BubbleTail>\n )\n}\n\nexport default SentCard\n","import ComposerCard, {\n type ComposerCardProps,\n} from './components/Composer/Card'\nimport ReceivedCard, {\n type ReceivedCardProps,\n} from './components/Received/Card'\nimport SentCard, { type SentCardProps } from './components/Sent/Card'\n\n/**\n * Link previews (1P / 3P Link Apps) shown in the chat thread. Mirrors\n * the `LockedAttachment` API — render `LinkAttachment.Composer` while\n * drafting, `LinkAttachment.Sent` after posting, and\n * `LinkAttachment.Received` in the recipient's thread. Maps to the\n * \"LinkApps\" section of the messaging design system.\n *\n * Two visual layouts via the `layout` prop:\n * - **Featured** (default) — a hero thumbnail filling the card above the\n * chin, inside a fixed 290x250 box. Used by hero-image LinkApps\n * (Spotify with cover art, TikTok with a frame, etc.) and by link\n * previews carrying OG imagery.\n * - **Classic** — compact card with no hero thumbnail; title /\n * description / URL / CTA only, sized to its content. Used for LinkApp\n * embeds without artwork (FAQ, Form) and any link preview that lacks\n * OG imagery.\n *\n * Pass `accentColor` (a hex extracted from the thumbnail) to paint the card\n * and its bubble tail; the card derives its own WCAG AA text colour.\n *\n * For chat **document / image / video / audio attachments**, reach for\n * `MessageAttachment.{Image,Video,Audio,Pdf,File}` instead — those\n * render as bubbles with built-in viewers (zoom-capable image\n * lightbox, native PDF viewer, video / audio with download) and a\n * caption slot for accompanying text.\n */\nconst LinkAttachment = {\n Composer: ComposerCard,\n Sent: SentCard,\n Received: ReceivedCard,\n}\n\nexport default LinkAttachment\nexport type { ComposerCardProps, SentCardProps, ReceivedCardProps }\nexport type {\n LinkAttachmentBaseProps,\n LinkAttachmentCta,\n LinkAttachmentLayout,\n LinkAttachmentStatus,\n} from './types'\n","import React from 'react'\nimport type { Attachment, LocalMessage } from 'stream-chat'\nimport {\n MessageErrorIcon,\n MessageText,\n type MessageContextValue,\n} from 'stream-chat-react'\n\nimport { optimizeMessagingAttachmentUrl } from '../../utils/cdnImageUrl'\nimport LinkAttachment from '../LinkAttachment'\nimport { isPlayableMedia } from '../LinkAttachment/components/_shared/CardThumbnail'\nimport MessageAttachment from '../MessageAttachment'\nimport type { BubbleGroupPosition } from '../MessageAttachment/types'\n\ntype StreamMediaKind = 'image' | 'video' | 'audio' | 'pdf' | 'file'\n\nexport type AttachmentSegment =\n | { type: 'link'; attachments: Attachment[] }\n | { type: 'media'; kind: StreamMediaKind; attachments: Attachment[] }\n\nfunction trimToUndefined(value?: string | null): string | undefined {\n const trimmed = value?.trim()\n return trimmed ? trimmed : undefined\n}\n\nexport function normalizeLinkUrl(url: string): string {\n return url\n .trim()\n .replace(/^https?:\\/\\//i, '')\n .replace(/\\/+$/, '')\n .toLowerCase()\n}\n\nexport function linkCaptionDuplicatesAttachmentUrl(\n caption: string | undefined,\n attachmentUrl?: string\n): boolean {\n if (!caption?.trim() || !attachmentUrl?.trim()) {\n return false\n }\n\n const normalizedCaption = normalizeLinkUrl(caption)\n const normalizedUrl = normalizeLinkUrl(attachmentUrl)\n\n if (normalizedCaption === normalizedUrl) {\n return true\n }\n\n if (!normalizedCaption.includes(normalizedUrl)) {\n return false\n }\n\n const remainder = normalizedCaption.split(normalizedUrl).join('')\n return !/[^\\p{P}\\p{Z}\\p{C}]/u.test(remainder)\n}\n\nexport function linkCardPropsFromStreamAttachment(a: Attachment) {\n const url = trimToUndefined(a.og_scrape_url) ?? trimToUndefined(a.title_link)\n const thumbnailUrl =\n trimToUndefined((a as { image_url?: string }).image_url) ??\n trimToUndefined((a as { thumb_url?: string }).thumb_url)\n const sourceUrl = trimToUndefined(a.asset_url)\n const mimeType = trimToUndefined(a.mime_type)\n\n return {\n title: trimToUndefined((a as { title?: string }).title),\n description: trimToUndefined((a as { text?: string }).text),\n url,\n thumbnailUrl: optimizeMessagingAttachmentUrl(thumbnailUrl),\n // Playable clip embedded in a link preview — lets LinkAttachment render its\n // native video/audio player in the hero instead of a static thumbnail.\n sourceUrl,\n layout:\n thumbnailUrl || isPlayableMedia(mimeType, sourceUrl)\n ? 'featured'\n : 'classic',\n mimeType,\n // Dominant colour of the image, resolved by the sender at stage time and\n // persisted on the attachment (`stream-custom-data.ts`), so every viewer\n // paints an identical chin without re-extracting — visitors in\n // linktr.ee-profiles cannot call the extraction query at all. Absent on\n // messages sent before MES-1349, which render the fallback chin.\n accentColor: trimToUndefined(a.accent_color),\n // A persisted attachment is by definition fully scraped.\n status: 'ready',\n } as const\n}\n\nexport function getAttachmentMediaKind(\n attachment: Attachment\n): StreamMediaKind | null {\n if (isLinkAttachment(attachment)) return null\n\n if (attachment.type === 'image') {\n const imageUrl = (attachment as { image_url?: string }).image_url\n if (imageUrl || attachment.asset_url) return 'image'\n return null\n }\n\n if (attachment.type === 'video' && attachment.asset_url) return 'video'\n if (attachment.type === 'audio' && attachment.asset_url) return 'audio'\n\n if (attachment.type === 'file' && attachment.asset_url) {\n if (attachment.mime_type === 'application/pdf') return 'pdf'\n if (attachment.mime_type?.startsWith('audio/')) return 'audio'\n return 'file'\n }\n\n return null\n}\n\nexport function isLinkAttachment(a: Attachment): boolean {\n const ogScrapeUrl = trimToUndefined(a.og_scrape_url)\n return a.type === 'link' || (!!ogScrapeUrl && !a.asset_url)\n}\n\nexport function buildOrderedAttachmentSegments(\n attachments: Attachment[] | undefined\n): AttachmentSegment[] {\n if (!attachments?.length) return []\n\n const segments: AttachmentSegment[] = []\n\n const pushOrMergeMedia = (kind: StreamMediaKind, attachment: Attachment) => {\n const last = segments[segments.length - 1]\n if (last?.type === 'media' && last.kind === kind) {\n last.attachments.push(attachment)\n return\n }\n\n segments.push({ type: 'media', kind, attachments: [attachment] })\n }\n\n const pushOrMergeLink = (attachment: Attachment) => {\n const last = segments[segments.length - 1]\n if (last?.type === 'link') {\n last.attachments.push(attachment)\n return\n }\n\n segments.push({ type: 'link', attachments: [attachment] })\n }\n\n for (const attachment of attachments) {\n if (isLinkAttachment(attachment)) {\n pushOrMergeLink(attachment)\n continue\n }\n\n const kind = getAttachmentMediaKind(attachment)\n if (!kind) continue\n\n pushOrMergeMedia(kind, attachment)\n }\n\n return segments\n}\n\nexport function countMediaSegmentBubbles(\n segments: AttachmentSegment[]\n): number {\n return segments.filter((segment) => segment.type === 'media').length\n}\n\nexport function bubblePositionInRun(\n index: number,\n total: number,\n streamPosition: BubbleGroupPosition\n): BubbleGroupPosition {\n if (total <= 1) return streamPosition\n if (index === 0) return 'first'\n if (index === total - 1) return 'end'\n return 'middle'\n}\n\nfunction imageSrcFromAttachment(attachment: Attachment): string {\n return (\n trimToUndefined((attachment as { image_url?: string }).image_url) ??\n trimToUndefined(attachment.asset_url) ??\n ''\n )\n}\n\nfunction messageAttachmentNamespaceForKind(kind: StreamMediaKind) {\n switch (kind) {\n case 'image':\n return MessageAttachment.Image\n case 'video':\n return MessageAttachment.Video\n case 'audio':\n return MessageAttachment.Audio\n case 'pdf':\n return MessageAttachment.Pdf\n default:\n return MessageAttachment.File\n }\n}\n\nfunction buildMediaClusterRenderProps(\n kind: StreamMediaKind,\n attachments: Attachment[]\n): Record<string, unknown> {\n if (attachments.length > 1) {\n switch (kind) {\n case 'image':\n return {\n items: attachments.map((attachment) => ({\n src: imageSrcFromAttachment(attachment),\n alt: trimToUndefined((attachment as { title?: string }).title),\n })),\n }\n case 'video':\n return {\n items: attachments.map((attachment) => ({\n src: trimToUndefined(attachment.asset_url) ?? '',\n poster: trimToUndefined(\n (attachment as { thumb_url?: string }).thumb_url\n ),\n mimeType: trimToUndefined(attachment.mime_type),\n })),\n }\n case 'audio':\n return {\n items: attachments.map((attachment) => ({\n src: trimToUndefined(attachment.asset_url) ?? '',\n mimeType: trimToUndefined(attachment.mime_type) ?? 'audio/mpeg',\n filename: trimToUndefined((attachment as { title?: string }).title),\n })),\n }\n case 'pdf':\n return {\n items: attachments.map((attachment) => ({\n src: trimToUndefined(attachment.asset_url) ?? '',\n filename: trimToUndefined((attachment as { title?: string }).title),\n fileSize: (attachment as { file_size?: number }).file_size,\n })),\n }\n case 'file':\n return {\n items: attachments.map((attachment) => ({\n src: trimToUndefined(attachment.asset_url) ?? '',\n filename: trimToUndefined((attachment as { title?: string }).title),\n fileSize: (attachment as { file_size?: number }).file_size,\n mimeType:\n trimToUndefined(attachment.mime_type) ??\n 'application/octet-stream',\n })),\n }\n }\n }\n\n const attachment = attachments[0]\n if (!attachment) return {}\n\n switch (kind) {\n case 'image':\n return {\n src: imageSrcFromAttachment(attachment),\n alt: trimToUndefined((attachment as { title?: string }).title),\n }\n case 'video':\n return {\n src: trimToUndefined(attachment.asset_url) ?? '',\n poster: trimToUndefined(\n (attachment as { thumb_url?: string }).thumb_url\n ),\n mimeType: trimToUndefined(attachment.mime_type),\n }\n case 'audio':\n return {\n src: trimToUndefined(attachment.asset_url) ?? '',\n mimeType: trimToUndefined(attachment.mime_type) ?? 'audio/mpeg',\n filename: trimToUndefined((attachment as { title?: string }).title),\n }\n case 'pdf':\n return {\n src: trimToUndefined(attachment.asset_url) ?? '',\n filename: trimToUndefined((attachment as { title?: string }).title),\n fileSize: (attachment as { file_size?: number }).file_size,\n }\n case 'file':\n return {\n src: trimToUndefined(attachment.asset_url) ?? '',\n filename: trimToUndefined((attachment as { title?: string }).title),\n fileSize: (attachment as { file_size?: number }).file_size,\n mimeType:\n trimToUndefined(attachment.mime_type) ?? 'application/octet-stream',\n }\n }\n}\n\nfunction MediaClusterBubble({\n groupPosition,\n isMyMessage,\n segment,\n}: {\n groupPosition: BubbleGroupPosition\n isMyMessage: boolean\n segment: Extract<AttachmentSegment, { type: 'media' }>\n}) {\n const TypeNamespace = messageAttachmentNamespaceForKind(segment.kind)\n const props = {\n ...buildMediaClusterRenderProps(segment.kind, segment.attachments),\n groupPosition,\n }\n\n if (isMyMessage) {\n return <TypeNamespace.Sent {...props} />\n }\n\n return <TypeNamespace.Received {...props} />\n}\n\nexport interface StreamAttachmentMessageProps {\n groupPosition: BubbleGroupPosition\n isMyMessage: boolean\n message: LocalMessage\n renderText?: MessageContextValue['renderText']\n}\n\nconst StreamAttachmentMessage: React.FC<StreamAttachmentMessageProps> = ({\n groupPosition,\n isMyMessage,\n message,\n renderText,\n}) => {\n const segments = buildOrderedAttachmentSegments(message.attachments)\n if (segments.length === 0) return null\n\n const totalMediaBubbles = countMediaSegmentBubbles(segments)\n const rawCaption = message.text?.trim()\n const linkSegment = segments.find((segment) => segment.type === 'link')\n const primaryLinkUrl =\n linkSegment?.type === 'link'\n ? linkCardPropsFromStreamAttachment(linkSegment.attachments[0] ?? {}).url\n : undefined\n const caption =\n rawCaption && linkCaptionDuplicatesAttachmentUrl(rawCaption, primaryLinkUrl)\n ? undefined\n : rawCaption\n\n let mediaBubbleIndex = 0\n const rows: React.ReactNode[] = []\n\n // A link card paints its own accent-coloured tail only when it is the very\n // last thing this message renders. A caption bubble follows when there is\n // one, and that bubble already owns the tail via `styles.css`; and a message\n // mid-run has its tail on a later message. Anything else is a mid-run card,\n // which must stay tail-less.\n const linkTailPosition: BubbleGroupPosition = caption\n ? 'middle'\n : groupPosition\n\n const pushLinkRow = (\n attachment: Attachment,\n key: string,\n isFinalRow: boolean\n ) => {\n const Card = isMyMessage ? LinkAttachment.Sent : LinkAttachment.Received\n\n rows.push(\n <Card\n key={key}\n {...linkCardPropsFromStreamAttachment(attachment)}\n groupPosition={isFinalRow ? linkTailPosition : 'middle'}\n />\n )\n }\n\n const pushMediaCluster = (\n segment: Extract<AttachmentSegment, { type: 'media' }>,\n key: string\n ) => {\n const position = bubblePositionInRun(\n mediaBubbleIndex,\n totalMediaBubbles,\n groupPosition\n )\n\n mediaBubbleIndex += 1\n\n rows.push(\n <MediaClusterBubble\n key={key}\n groupPosition={position}\n isMyMessage={isMyMessage}\n segment={segment}\n />\n )\n }\n\n segments.forEach((segment, segmentIndex) => {\n const isFinalSegment = segmentIndex === segments.length - 1\n\n if (segment.type === 'link') {\n segment.attachments.forEach((attachment, attachmentIndex) => {\n pushLinkRow(\n attachment,\n `link-${segmentIndex}-${attachmentIndex}`,\n isFinalSegment && attachmentIndex === segment.attachments.length - 1\n )\n })\n return\n }\n\n pushMediaCluster(segment, `media-${segmentIndex}`)\n })\n\n return (\n <div className=\"str-chat__message-bubble-wrapper\">\n <div\n className=\"str-chat__message-bubble\"\n data-attachment-bubble=\"true\"\n style={{\n background: 'transparent',\n borderRadius: 0,\n overflow: 'visible',\n padding: 0,\n }}\n >\n {rows}\n {!caption && <MessageErrorIcon />}\n </div>\n {caption ? (\n <div className=\"str-chat__message-bubble\">\n <MessageText message={message} renderText={renderText} />\n <MessageErrorIcon />\n </div>\n ) : null}\n </div>\n )\n}\n\nexport default StreamAttachmentMessage\n","import classNames from 'classnames'\nimport React, { useContext, useMemo, useState } from 'react'\nimport {\n Attachment as DefaultAttachment,\n EditMessageModal as DefaultEditMessageModal,\n MessageBounceModal,\n MessageBouncePrompt as DefaultMessageBouncePrompt,\n MessageBlocked as DefaultMessageBlocked,\n MessageDeleted as DefaultMessageDeleted,\n MessageErrorIcon,\n MessageIsThreadReplyInChannelButtonIndicator as DefaultMessageIsThreadReplyInChannelButtonIndicator,\n MessageRepliesCountButton as DefaultMessageRepliesCountButton,\n MessageText,\n Poll,\n ReminderNotification as DefaultReminderNotification,\n StreamedMessageText as DefaultStreamedMessageText,\n areMessageUIPropsEqual,\n isDateSeparatorMessage,\n isMessageBlocked,\n isMessageBounced,\n messageHasAttachments,\n messageHasReactions,\n useChannelStateContext,\n useComponentContext,\n useChatContext,\n useMessageContext,\n useMessageReminder,\n type MessageContextValue,\n type MessageUIComponentProps,\n} from 'stream-chat-react'\n\nimport { useMessageVote } from '../../hooks/useMessageVote'\nimport { getMessageDisplayText } from '../../utils/getMessageDisplayText'\nimport { Avatar } from '../Avatar'\nimport { isLinkAttachment } from '../MediaMessage'\nimport { bubbleGroupPositionFromStream as messageAttachmentGroupPositionFromStream } from '../MessageAttachment'\n\nimport { BroadcastNameContext } from './BroadcastNameContext'\nimport { useCustomMessage } from './context'\nimport LockedAttachment from './LockedAttachment'\nimport {\n MessageTag,\n isAttachmentMessage,\n isChatbotMessage,\n isMediaAttachmentMessage,\n isTextAttachmentMessage,\n isTipOnlyMessage,\n} from './MessageTag'\nimport { MessageVoteButtons } from './MessageVoteButtons'\nimport { SentMessageDeliveryStatus } from './SentMessageDeliveryStatus'\nimport StreamAttachmentMessage, {\n buildOrderedAttachmentSegments,\n} from './StreamAttachmentMessage'\n\ntype CustomMessageUIComponentProps = MessageUIComponentProps & {\n chatbotVotingEnabled?: boolean\n viewerLanguage?: string\n onBroadcastClick?: (broadcast: { id: string; name?: string }) => void\n}\n\ntype CustomMessageWithContextProps = MessageContextValue & {\n chatbotVotingEnabled?: boolean\n viewerLanguage?: string\n onBroadcastClick?: (broadcast: { id: string; name?: string }) => void\n resolvedBroadcastName?: string\n}\n\nconst CustomMessageWithContext = (props: CustomMessageWithContextProps) => {\n const {\n additionalMessageInputProps,\n chatbotVotingEnabled,\n editing,\n endOfGroup,\n firstOfGroup,\n groupedByUser,\n handleAction,\n handleOpenThread,\n handleRetry,\n highlighted,\n isMessageAIGenerated,\n isMyMessage,\n message,\n renderText,\n threadList,\n viewerLanguage,\n onBroadcastClick,\n resolvedBroadcastName,\n } = props\n\n const { client } = useChatContext('CustomMessage')\n const { channel } = useChannelStateContext('CustomMessage')\n const { isUnlocking, onUnlockClick, onFetchSource, onDownloadClick } =\n useCustomMessage('LockedAttachment')\n const [isBounceDialogOpen, setIsBounceDialogOpen] = useState(false)\n const reminder = useMessageReminder(message.id)\n const { selected: voteState, voteUp, voteDown } = useMessageVote(message)\n\n const {\n Attachment = DefaultAttachment,\n EditMessageModal = DefaultEditMessageModal,\n MessageActions,\n MessageBlocked = DefaultMessageBlocked,\n MessageBouncePrompt = DefaultMessageBouncePrompt,\n MessageDeleted = DefaultMessageDeleted,\n MessageIsThreadReplyInChannelButtonIndicator = DefaultMessageIsThreadReplyInChannelButtonIndicator,\n MessageRepliesCountButton = DefaultMessageRepliesCountButton,\n ReminderNotification = DefaultReminderNotification,\n StreamedMessageText = DefaultStreamedMessageText,\n PinIndicator,\n } = useComponentContext('CustomMessage')\n\n const hasAttachment = messageHasAttachments(message)\n const hasReactions = messageHasReactions(message)\n const isAIGenerated = useMemo(\n () => isMessageAIGenerated?.(message),\n [isMessageAIGenerated, message]\n )\n const finalAttachments = useMemo(() => {\n const attachments = message.attachments ?? []\n const raw = message.shared_location\n ? [message.shared_location, ...attachments]\n : attachments\n\n if (!isChatbotMessage(message)) return raw\n\n const filtered = raw.filter((a) => !('type' in a) || !isLinkAttachment(a))\n return filtered.length === raw.length ? raw : filtered\n }, [message])\n const attachmentSegments = useMemo(\n () => buildOrderedAttachmentSegments(finalAttachments),\n [finalAttachments]\n )\n const displayMessage = useMemo(() => {\n const displayText = getMessageDisplayText({ message, viewerLanguage })\n return displayText === message.text\n ? message\n : { ...message, text: displayText }\n }, [message, viewerLanguage])\n // Route every generic Stream-attachment message (link OG previews and\n // image/video/audio/pdf/file media) through the toolkit renderer, so\n // messaging-react owns all shared attachment rendering rather than leaving\n // media on stream-chat-react's default Attachment. App-specific types\n // (locked/paid, tips, chatbot) are handled by the branches above.\n const hasAttachmentSegments = attachmentSegments.length > 0\n // Only own the message when every attachment maps to a representable segment.\n // Otherwise a mixed message (e.g. an unsupported shared_location alongside a\n // photo) would silently drop the unrepresented attachment — those fall\n // through to the default `Attachment` renderer, which handles all of them.\n const allAttachmentsRepresented =\n attachmentSegments.reduce(\n (total, segment) => total + segment.attachments.length,\n 0\n ) === (finalAttachments?.length ?? 0)\n const streamAttachmentGroupPosition =\n messageAttachmentGroupPositionFromStream({\n endOfGroup,\n firstOfGroup,\n groupedByUser,\n })\n\n if (isDateSeparatorMessage(message)) {\n return null\n }\n\n if (message.deleted_at || message.type === 'deleted') {\n return <MessageDeleted message={message} />\n }\n\n if (isMessageBlocked(message)) {\n return <MessageBlocked />\n }\n\n const showReplyCountButton = !threadList && !!message.reply_count\n const showIsReplyInChannel =\n !threadList && message.show_in_channel && message.parent_id\n const allowRetry =\n message.status === 'failed' && message.error?.status !== 403\n const isBounced = isMessageBounced(message)\n let handleClick: (() => void) | undefined = undefined\n\n if (allowRetry) {\n handleClick = () => handleRetry(message)\n } else if (isBounced) {\n handleClick = () => setIsBounceDialogOpen(true)\n }\n\n const isMine = isMyMessage()\n const rootClassName = classNames(\n 'str-chat__message str-chat__message-simple',\n `str-chat__message--${message.type}`,\n `str-chat__message--${message.status}`,\n isMine\n ? 'str-chat__message--me str-chat__message-simple--me'\n : 'str-chat__message--other',\n message.text ? 'str-chat__message--has-text' : 'has-no-text',\n {\n 'str-chat__message--has-attachment': hasAttachment,\n 'str-chat__message--highlighted': highlighted,\n 'str-chat__message--pinned pinned-message': message.pinned,\n 'str-chat__message--with-reactions': hasReactions,\n 'str-chat__message-send-can-be-retried':\n message?.status === 'failed' && message?.error?.status !== 403,\n 'str-chat__message-with-thread-link':\n showReplyCountButton || showIsReplyInChannel,\n 'str-chat__virtual-message__wrapper--end': endOfGroup,\n 'str-chat__virtual-message__wrapper--first': firstOfGroup,\n 'str-chat__virtual-message__wrapper--group': groupedByUser,\n }\n )\n\n const poll = message.poll_id && client.polls.fromState(message.poll_id)\n const isTipOnly = isTipOnlyMessage(message)\n const isChatbot = isChatbotMessage(message)\n const isAttachment = isAttachmentMessage(message)\n const hasRenderableAttachments = !!(\n finalAttachments?.length && !message.quoted_message\n )\n const useAttachmentFooterChatbotTag =\n isChatbot && isMine && hasRenderableAttachments\n // Route generic Stream attachments through the toolkit renderer only when it\n // can faithfully own the whole message. Quoted replies (attachments are\n // suppressed there), polls, AI-streamed, and chatbot messages fall through to\n // the default branch so their Poll / StreamedMessageText / message-text and\n // chatbot attribution (MessageTag) handling is preserved — the accompanying\n // message text and attribution must never be dropped.\n const canRenderAttachmentsInToolkit =\n hasAttachmentSegments &&\n allAttachmentsRepresented &&\n !message.quoted_message &&\n !poll &&\n !isAIGenerated &&\n !isChatbot\n\n const sharedLockedTextProps = {\n contentType: 'text' as const,\n amountText: message.metadata?.amount_text,\n paymentStatus: message.metadata?.payment_status,\n renderedText: message.text && (\n <MessageText message={displayMessage} renderText={renderText} />\n ),\n }\n const sharedLockedMediaProps = {\n contentType: 'media' as const,\n title: message.metadata?.attachment_title,\n mimeType: message.metadata?.attachment_mime_type,\n thumbnailUrl: message.metadata?.attachment_thumbnail,\n amountText: message.metadata?.amount_text,\n detail: message.metadata?.attachment_detail,\n paymentStatus: message.metadata?.payment_status,\n onFetchSource: onFetchSource\n ? () => onFetchSource(message, channel)\n : undefined,\n }\n return (\n <>\n {editing && (\n <EditMessageModal\n additionalMessageInputProps={additionalMessageInputProps}\n />\n )}\n {isBounceDialogOpen && (\n <MessageBounceModal\n MessageBouncePrompt={MessageBouncePrompt}\n onClose={() => setIsBounceDialogOpen(false)}\n open={isBounceDialogOpen}\n />\n )}\n <div\n className={rootClassName}\n key={message.id}\n data-message-id={message.id}\n data-dd-privacy=\"mask\"\n >\n {PinIndicator && <PinIndicator />}\n {!!reminder && <ReminderNotification reminder={reminder} />}\n {message.user && (\n <Avatar\n className=\"str-chat__avatar str-chat__message-sender-avatar\"\n id={message.user.id}\n image={message.user.image}\n name={message.user.name || message.user.id}\n size={24}\n shape=\"circle\"\n dmAgentEnabled={isChatbot}\n />\n )}\n <div\n className={classNames('str-chat__message-inner', {\n 'str-chat__simple-message--error-failed': allowRetry || isBounced,\n })}\n data-testid=\"message-inner\"\n onClick={handleClick}\n onKeyDown={handleClick}\n role={handleClick ? 'button' : undefined}\n tabIndex={handleClick ? 0 : undefined}\n style={{\n // Force margins to 0 to prevent hover layout shift\n // Stream Chat CSS sets margin-inline-end/start to 78px, then 0 on hover\n marginInlineEnd: 0,\n marginInlineStart: 0,\n }}\n >\n {isTipOnly ? (\n <MessageTag message={message} standalone />\n ) : isTextAttachmentMessage(message) ? (\n <div className=\"str-chat__message-bubble-wrapper\">\n <div className=\"flex items-center gap-2\">\n {isMine && MessageActions && <MessageActions />}\n {isMine ? (\n <LockedAttachment\n {...sharedLockedTextProps}\n isMine={true}\n />\n ) : (\n <LockedAttachment\n {...sharedLockedTextProps}\n isMine={false}\n isUnlocking={isUnlocking(message.id)}\n onUnlockClick={() => onUnlockClick?.(message, channel)}\n />\n )}\n {!isMine && MessageActions && <MessageActions />}\n </div>\n </div>\n ) : isMediaAttachmentMessage(message) ? (\n <div className=\"str-chat__message-bubble-wrapper\">\n <div className=\"flex items-center gap-2\">\n {isMine && MessageActions && <MessageActions />}\n {isMine ? (\n <LockedAttachment\n {...sharedLockedMediaProps}\n isMine={true}\n onPreviewClick={() =>\n onUnlockClick\n ? onUnlockClick(message, channel)\n : undefined\n }\n />\n ) : (\n <LockedAttachment\n {...sharedLockedMediaProps}\n isMine={false}\n isUnlocking={isUnlocking(message.id)}\n onUnlockClick={() =>\n onUnlockClick\n ? onUnlockClick(message, channel)\n : undefined\n }\n onDownloadClick={() =>\n onDownloadClick\n ? onDownloadClick(message, channel)\n : undefined\n }\n />\n )}\n {!isMine && MessageActions && <MessageActions />}\n </div>\n {message.text && (\n <div className=\"str-chat__message-bubble\">\n <MessageText\n message={displayMessage}\n renderText={renderText}\n />\n </div>\n )}\n </div>\n ) : canRenderAttachmentsInToolkit ? (\n <StreamAttachmentMessage\n groupPosition={streamAttachmentGroupPosition}\n isMyMessage={isMine}\n message={{\n ...displayMessage,\n attachments: finalAttachments,\n // MessageText prefers message.i18n[userLanguage_text] from\n // Stream's TranslationContext over message.text. Drop i18n so\n // the caption uses displayMessage.text from viewerLanguage.\n i18n: undefined,\n }}\n renderText={renderText}\n />\n ) : (\n <div className=\"str-chat__message-bubble-wrapper\">\n <div className=\"str-chat__message-bubble\">\n {isChatbot && !useAttachmentFooterChatbotTag && (\n <MessageTag\n message={message}\n hasAttachment={hasRenderableAttachments}\n isMyMessage={isMine}\n />\n )}\n {poll && <Poll poll={poll} />}\n {finalAttachments?.length && !message.quoted_message ? (\n <Attachment\n actionHandler={handleAction}\n attachments={finalAttachments}\n />\n ) : null}\n {isAIGenerated ? (\n <StreamedMessageText\n message={displayMessage}\n renderText={renderText}\n />\n ) : (\n <MessageText\n message={displayMessage}\n renderText={renderText}\n />\n )}\n <MessageErrorIcon />\n </div>\n </div>\n )}\n </div>\n {!isAttachment && !isTipOnly && (\n <div className=\"str-chat__message-footer\">\n {(!isChatbot || useAttachmentFooterChatbotTag) && (\n <MessageTag\n message={message}\n hasAttachment={hasRenderableAttachments}\n isMyMessage={isMine}\n />\n )}\n {chatbotVotingEnabled && isChatbot && (\n <MessageVoteButtons\n selected={voteState}\n onVoteUp={voteUp}\n onVoteDown={voteDown}\n />\n )}\n </div>\n )}\n <SentMessageDeliveryStatus\n onBroadcastClick={onBroadcastClick}\n resolvedBroadcastName={resolvedBroadcastName}\n />\n {showReplyCountButton && (\n <MessageRepliesCountButton\n onClick={handleOpenThread}\n reply_count={message.reply_count}\n />\n )}\n {showIsReplyInChannel && (\n <MessageIsThreadReplyInChannelButtonIndicator />\n )}\n </div>\n </>\n )\n}\n\nconst MemoizedCustomMessage = React.memo(\n CustomMessageWithContext,\n (prev, next) => {\n if (prev.chatbotVotingEnabled !== next.chatbotVotingEnabled) return false\n if (prev.viewerLanguage !== next.viewerLanguage) return false\n if (prev.onBroadcastClick !== next.onBroadcastClick) return false\n if (prev.resolvedBroadcastName !== next.resolvedBroadcastName) return false\n return areMessageUIPropsEqual(prev, next)\n }\n) as typeof CustomMessageWithContext\n\nexport const CustomMessage = (props: CustomMessageUIComponentProps) => {\n const messageContext = useMessageContext('CustomMessage')\n const broadcastNamesById = useContext(BroadcastNameContext)\n const broadcastId = messageContext.message.metadata?.broadcast_id\n const resolvedBroadcastName =\n typeof broadcastId === 'string'\n ? broadcastNamesById?.[broadcastId]\n : undefined\n\n return (\n <MemoizedCustomMessage\n {...messageContext}\n {...props}\n resolvedBroadcastName={resolvedBroadcastName}\n />\n )\n}\n","import { MESSAGE_CUSTOM_TYPE } from '@linktr.ee/messaging-taxonomy'\nimport { FlagIcon, TrashSimpleIcon } from '@phosphor-icons/react'\nimport React from 'react'\nimport { useMessageContext } from 'stream-chat-react'\nimport {\n DefaultDropdownActionButton,\n MessageActions,\n} from 'stream-chat-react/experimental'\n\nconst DeleteAction = () => {\n const { handleDelete, message } = useMessageContext('CustomMessageActions')\n if (message.metadata?.payment_status === 'paid') return null\n return (\n <DefaultDropdownActionButton\n onClick={handleDelete}\n aria-label=\"Delete\"\n title=\"Delete\"\n className=\"bg-marble rounded-full p-2 hover:bg-sand transition-all\"\n >\n <TrashSimpleIcon size={16} weight=\"light\" aria-hidden />\n </DefaultDropdownActionButton>\n )\n}\n\nconst ReportAction = () => {\n const { handleFlag } = useMessageContext('CustomMessageActions')\n return (\n <DefaultDropdownActionButton\n onClick={handleFlag}\n aria-label=\"Report\"\n title=\"Report\"\n className=\"bg-marble rounded-full p-2 hover:bg-sand transition-all\"\n >\n <FlagIcon size={16} weight=\"light\" aria-hidden />\n </DefaultDropdownActionButton>\n )\n}\n\nexport const CustomMessageActions = () => {\n const { message } = useMessageContext('CustomMessageActions')\n if (message.metadata?.custom_type !== MESSAGE_CUSTOM_TYPE.ATTACHMENT)\n return null\n return (\n <MessageActions\n messageActionSet={[\n { Component: DeleteAction, placement: 'quick', type: 'delete' },\n { Component: ReportAction, placement: 'quick', type: 'flag' },\n ]}\n />\n )\n}\n","import React from 'react'\nimport { LinkPreviewsManager, LinkPreviewsManagerState } from 'stream-chat'\nimport { useMessageComposer, useStateStore } from 'stream-chat-react'\n\nimport LinkAttachment from '../LinkAttachment'\n\nconst linkPreviewsManagerStateSelector = (state: LinkPreviewsManagerState) => ({\n linkPreviews: Array.from(state.previews.values()).filter(\n (preview) =>\n LinkPreviewsManager.previewIsLoaded(preview) ||\n LinkPreviewsManager.previewIsLoading(preview)\n ),\n})\n\n/**\n * The composer's staged link previews, rendered as `LinkAttachment.Composer`\n * cards.\n *\n * **The cards are deliberately dismiss-only — they do not navigate.**\n * `LinkAttachment.Composer` is the one card variant whose shell is never an\n * anchor or a button, and the hand-rolled card this replaced wrapped its whole\n * body in `<a href={og_scrape_url} target=\"_blank\">` with the dismiss button\n * calling `preventDefault()` inside it. That shape is a touch hazard —\n * MES-1358 §7 Q7 raised it and deferred the call to MES-1349 — because a\n * mis-tap while composing opens a new tab mid-draft, and the dismiss button's\n * 44px hit halo (`Composer/Card.tsx`, `before:-inset-[10px]`) sits *inside*\n * that anchor, so enlarging the target for touch enlarges the navigation\n * trigger with it. A staged preview is a draft affordance rather than\n * content — its URL is still in the textarea the author just typed it into,\n * one tap away without leaving the thread.\n *\n * Nothing depended on the old behaviour: no test, story or analytics asserted\n * it here or in frontyard's parallel `ComposerLinkPreviewList`, whose own\n * tests cover text, dismiss and layout only. If a surface ever genuinely needs\n * activation, add an explicit prop to `ComposerCardProps` — do not re-wrap the\n * card in an anchor. (MES-1349)\n */\nexport const CustomLinkPreviewList = () => {\n const { linkPreviewsManager } = useMessageComposer()\n\n const { linkPreviews: stateLinkPreviews } = useStateStore(\n linkPreviewsManager.state,\n linkPreviewsManagerStateSelector\n )\n\n const handleDismiss = (url: string) => {\n linkPreviewsManager.dismissPreview(url)\n }\n\n const showLinkPreviews = stateLinkPreviews.length > 0\n\n if (!showLinkPreviews) return null\n\n return (\n // Layout contract: this list is NOT a scrollable or shrinkable region. It\n // used to be the composer's one squeezable box, giving way (min-h-0) and\n // scrolling on its own so the textarea row could stay pinned above the\n // keyboard. That cropped the preview to make room for the input, which read\n // as the preview being cut off; MES-1037 inverted it. The composer card\n // (`.central-container`) is now the single capped scroll container and this\n // list scrolls as one piece with the textarea inside it, so the preview\n // always keeps its full height and only the visible slice of it changes.\n // Do not reintroduce a nested scroller or a min-h-0 here.\n //\n // Horizontally the card does NOT shrink, deliberately. `CardShell` bakes\n // Figma's `w-[290px]` and there is exactly one width, in the toolkit\n // (MES-1349 and MES-1358 agreed consumers add no competing cap). Do not\n // \"fix\" a narrow viewport with `max-w-full` here or on the shell: the box is\n // `flex h-[250px] flex-col` with a `flex-1` hero and a title clamped to two\n // lines against a known width, so capping deforms it on every narrow\n // surface, in-thread Sent/Received included.\n //\n // Measured against the real class chain in headless Chromium:\n //\n // chrome = 32px (`CustomMessageInput`'s outer `p-4`)\n // + 16px (this region's `px-2`) = 48px\n // card = 290px -> floor = 290 + 48 = 338px viewport\n //\n // viewport 320 360 375 393 430\n // available 272 312 327 345 382\n // delta -18 +22 +37 +55 +92\n //\n // 393px is the design frame, and its 345px reproduces the frame's measured\n // `attachment-area` width, so the chrome arithmetic is corroborated. The\n // lowest named breakpoint is `xs: 359px`\n // (`@repo/tailwind-config/transitional-tokens.js:29-36`), which is *above*\n // the 338px floor — the card fits at every width the scale names. MES-1358's\n // `max-md:p-3` trims chrome 48px -> 40px and moves the floor to 330px, still\n // above 320px, so capping `CardShell` would be the only way to reach a 320px\n // viewport. That is the whole trade: deform every narrow surface, or leave\n // one sub-floor device class awkward.\n //\n // It is awkward, not broken. The card above sets only `overflow-y`, which\n // promotes the other axis, so its `overflow-x` computes to `auto` and it\n // scrolls sideways instead of spilling; at 320px the dismiss button's right\n // edge lands ~6px past the fold, reachable by scrolling. That residue\n // belongs to whoever owns the mobile layout contract, not to this component.\n <div className=\"messaging-link-preview-list flex flex-col items-stretch w-full gap-2\">\n {stateLinkPreviews.map((linkPreview) => {\n // A LOADING preview has no `image_url` yet, so keying the layout off\n // the image alone rendered the skeleton as two bare text bars and then\n // snapped it open to the 250px featured box the instant the scrape\n // returned. Hold the hero's space for the duration instead — reserving\n // that space is what the skeleton is for.\n const isLoading = LinkPreviewsManager.previewIsLoading(linkPreview)\n\n return (\n <LinkAttachment.Composer\n key={linkPreview.og_scrape_url}\n title={linkPreview.title}\n url={linkPreview.og_scrape_url}\n thumbnailUrl={linkPreview.image_url}\n layout={isLoading || linkPreview.image_url ? 'featured' : 'classic'}\n status={isLoading ? 'loading' : 'ready'}\n onDismiss={() => handleDismiss(linkPreview.og_scrape_url)}\n />\n )\n })}\n </div>\n )\n}\n","import { ArrowUpIcon } from '@phosphor-icons/react'\nimport React, { useContext } from 'react'\nimport {\n AttachmentPreviewList as DefaultAttachmentPreviewList,\n MessageInput,\n QuotedMessagePreview,\n TextareaComposer,\n useChannelStateContext,\n useComponentContext,\n useMessageComposerHasSendableData,\n useMessageInputContext,\n} from 'stream-chat-react'\n\nimport { CustomLinkPreviewList } from '../CustomLinkPreviewList'\n\n/**\n * Carries the channel's `frozen` state down to CustomMessageInputInner, which\n * renders the composer as a read-only/disabled input. Using a context — rather\n * than closing the value into the `Input` component passed to Stream's\n * <MessageInput> — keeps CustomMessageInputInner a stable reference, so the\n * value toggling re-renders the composer instead of unmounting and remounting\n * it (which would drop textarea focus and interrupt IME composition).\n */\nconst ComposerLockedContext = React.createContext(false)\n\n/**\n * Distance from the bottom (px) still treated as \"at the latest message\", used\n * to decide whether a growing composer should re-pin the thread to the bottom\n * (MES-1303). Matches stream-chat-react's own `scrolledUpThreshold` default\n * (200) so composer growth keeps the thread pinned under the same condition\n * Stream already auto-scrolls a new message — anything closer than one message\n * would let a user resting just off the bottom fall through and get covered.\n */\nconst NEAR_BOTTOM_THRESHOLD_PX = 200\n\n/**\n * Read the composer's locked (frozen-channel) state from inside a custom\n * composer supplied via `composerInput`. Returns true when the channel is\n * frozen; mirror the default composer by rendering the textarea read-only and\n * disabling the send control.\n */\nexport const useComposerLocked = (): boolean =>\n useContext(ComposerLockedContext)\n\nconst DefaultSendButton: React.FC<{\n sendMessage: () => void\n disabled?: boolean\n [key: string]: unknown\n}> = ({ sendMessage, disabled, ...rest }) => (\n <button\n {...rest}\n type=\"button\"\n aria-label=\"Send\"\n disabled={disabled}\n // Keep the soft keyboard open after tapping send (MES-1320). Pressing a\n // button moves focus to it, blurring the textarea — on mobile that\n // dismisses the on-screen keyboard, forcing the visitor to re-tap the\n // composer for every message. preventDefault on pointerdown suppresses that\n // focus shift so the textarea stays focused; the click still fires (click is\n // derived from pointerup, not pointerdown), so `sendMessage` runs as normal.\n // pointerdown covers both touch and mouse, and keyboard activation (Enter/\n // Space → click) is unaffected.\n onPointerDown={(e) => e.preventDefault()}\n onClick={sendMessage}\n >\n <ArrowUpIcon weight=\"bold\" className=\"size-4\" />\n </button>\n)\n\nconst CustomMessageInputInner: React.FC = () => {\n const disabled = useContext(ComposerLockedContext)\n const { handleSubmit } = useMessageInputContext()\n\n const hasSendableData = useMessageComposerHasSendableData()\n const isSendDisabled = disabled || !hasSendableData\n\n const {\n SendButton = DefaultSendButton,\n AttachmentPreviewList = DefaultAttachmentPreviewList,\n } = useComponentContext('CustomMessageInput')\n\n // Scroll the textarea back into view when the card shrinks under it. Browsers\n // reveal a newly focused element by themselves, and the caret while typing, so\n // neither needs help; the one thing nothing reveals is the box getting shorter\n // underneath the input — which is exactly what the keyboard does here, a beat\n // after the tap that raised it, once the host reports it and the cap below\n // follows the shrunken surface (MES-1037).\n const cardRef = React.useRef<HTMLDivElement>(null)\n React.useEffect(() => {\n const card = cardRef.current\n\n if (!card || typeof ResizeObserver === 'undefined') {\n return\n }\n\n const observer = new ResizeObserver(() => {\n if (card.contains(document.activeElement)) {\n card.scrollTop = card.scrollHeight\n }\n })\n observer.observe(card)\n\n return () => observer.disconnect()\n }, [])\n\n // Let the browser insert pasted text itself, so the composer grows to fit it\n // immediately (MES-1037). Stream's paste handler calls preventDefault and\n // feeds the text back through composer state asynchronously; TextareaComposer\n // then assigns `textarea.value` in a layout effect, which fires no input event\n // and so never triggers react-textarea-autosize's re-measure — leaving the\n // composer one line tall, with the pasted text hidden, until the next\n // keystroke happens to resize it. Letting the default action run puts pasted\n // text on exactly the path typed text takes, which measures correctly.\n //\n // It has to be a native listener that stops propagation rather than an\n // `onPaste` prop: TextareaComposer takes `onPaste` from the message-input\n // context and applies it after spreading our props, so ours would be\n // overridden, and a React handler on this card would run after the textarea's\n // anyway. Stopping the event here keeps it from reaching React's root, where\n // Stream's handler is dispatched from. Pastes carrying a file still go to\n // Stream, which uploads it as an attachment.\n React.useEffect(() => {\n const card = cardRef.current\n\n if (!card) {\n return\n }\n\n const passToBrowser = (event: ClipboardEvent) => {\n // `items`, not `files`: Safari never populates `clipboardData.files` on\n // paste, even for an image, only `items` — which is why Stream's own\n // handler reads files via `items` (`dataTransferItemsToFiles`) rather\n // than `.files`. Checking `.files.length` here would treat a pasted\n // screenshot as plain text on Safari and drop it silently instead of\n // handing it to Stream to upload.\n const hasFile = Array.from(event.clipboardData?.items ?? []).some(\n (item) => item.kind === 'file'\n )\n if (!hasFile) {\n event.stopPropagation()\n }\n }\n\n card.addEventListener('paste', passToBrowser)\n\n return () => card.removeEventListener('paste', passToBrowser)\n }, [])\n\n return (\n // This card is the composer's ONE scroll container: staged previews and the\n // textarea row scroll together past a cap, instead of the previews being\n // squeezed to keep the row in view (MES-1037). Nothing inside may shrink, or\n // the previews get cropped rather than scrolled — hence no `min-h-0` here or\n // on the previews wrapper, no `shrink-0` on the row, and no nested scroller\n // in CustomLinkPreviewList. Every child keeps its intrinsic height and the\n // overflow lands on this scrollport. `self-stretch` went with the squeeze it\n // fed: the parent `.message-input` row is `items-end`, so the card now sizes\n // to its content up to the cap. Stream's <MessageInput> renders its Input\n // directly (context providers only, no wrapper element), so this is a direct\n // flex item of that row.\n //\n // The cap is half the *keyboard-shrunk* surface height, because a cap that\n // doesn't shrink with the keyboard lets the card spill past the visible\n // viewport — which is the bug. No CSS unit tracks the visual viewport, so\n // `--messaging-surface-height` is an optional host contract, like this\n // package's own `--messaging-composer-height`: linktr.ee-profiles publishes\n // it on the messaging surface (an ancestor of this composer at runtime) from\n // its own `visualViewport` measurement, and it inherits in. Hosts that don't\n // fall back to `100dvh` — bounded, but not keyboard-aware. Deliberately not\n // measured here: the host owns viewport control and the package must not\n // fight it for it.\n //\n // The container itself carries no padding of its own: it used to (p-2),\n // but that inset now lives on the textarea as padding instead, so the\n // textarea's actual (clickable) box is as large as the container — save\n // for the small margin and the send button on the right (MES-1325).\n // Visually nothing moves; the container ends up exactly the size of the\n // textarea + send button row it contains.\n //\n // Some hosts portal staged-media thumbnails (e.g. ComposerPendingMediaPortal,\n // see this component's stories) straight into `.central-container` rather\n // than through this React tree, bypassing the two wrappers below. The\n // `[&>:not(...)]:p-2` variant restores the container's old inset for any\n // such foreign direct child, without re-adding it to our own wrappers.\n <div\n ref={cardRef}\n className=\"central-container flex flex-col min-w-0 w-full max-h-[calc(var(--messaging-surface-height,100dvh)*0.5)] overflow-y-auto overscroll-contain bg-white rounded-[1.5rem] shadow-[0_4px_16px_0_rgba(0,0,0,0.08),0_1px_2px_0_rgba(0,0,0,0.04),0_0_0_1px_rgba(0,0,0,0.04)] [&>:not(.messaging-composer-previews):not(.messaging-composer-row)]:p-2\"\n data-dd-privacy=\"mask\"\n >\n {/* empty:hidden collapses this row (padding, gap, and all) when none of\n the previews below have anything to render, so an idle composer has\n no leftover top inset before the textarea row. No bottom inset: the\n textarea's own py-3.5 already separates the last preview from the\n text, and adding one here double-spaces them now that both sit in the\n same scroll. */}\n <div className=\"messaging-composer-previews empty:hidden flex flex-col gap-2 px-2 pt-2\">\n <QuotedMessagePreview />\n <CustomLinkPreviewList />\n <AttachmentPreviewList />\n </div>\n <div className=\"messaging-composer-row flex items-start\">\n <TextareaComposer\n aria-disabled={disabled || undefined}\n // TextareaComposer wraps the real <textarea> in its own div, so\n // `containerClassName` (not `className`) is what joins the row's\n // flex layout; `leading-[0]` drops the line-height space an\n // inline-level parent otherwise reserves, keeping that wrapper\n // exactly the textarea's height rather than stretched by the row\n // (items-start above is a second guard against that same stretch).\n containerClassName=\"min-w-0 flex-1 leading-[0]\"\n // bg-transparent: the container has no padding to inset it behind\n // anymore, so an opaque textarea would paint over its rounded corners.\n //\n // overflow-hidden makes \"the card is the only scrollable region\" a\n // property of the CSS rather than of the autosize arithmetic always\n // landing exactly right. A textarea's UA default is `overflow: auto`,\n // and TextareaComposer sizes itself by copying the computed style into\n // a detached measuring textarea on input, window resize and font load\n // only — so any width change it does not hear about (most obviously\n // this card gaining its own scrollbar, which narrows the textarea and\n // rewraps the text onto another line) leaves it a line short and\n // sprouts a second scrollbar inside the first. The caret stays visible\n // either way: browsers still scroll an overflow-hidden textarea to\n // reveal it, and the next keystroke re-fits the height.\n className=\"w-full resize-none overflow-hidden bg-transparent py-3.5 pl-4 pr-4 outline-none leading-5 placeholder:text-black/30 text-sm\"\n // While this might usually be considered an anti-pattern, in most\n // cases, when a message thread is rendered, we want the input to\n // gain focus automatically.\n // eslint-disable-next-line jsx-a11y/no-autofocus\n autoFocus={!disabled}\n // Enter sends (Shift+Enter inserts a newline), so label the\n // on-screen keyboard's return key accordingly.\n enterKeyHint=\"send\"\n // Grow to fit every line rather than scrolling the overflow inside\n // the textarea past a few of them: the card above is the one scroll\n // container, so a message long enough to outgrow it scrolls there, as\n // one piece with the previews. Explicit because TextareaComposer\n // defaults an absent `maxRows` to 1, not to \"no cap\".\n maxRows={Infinity}\n readOnly={disabled}\n tabIndex={disabled ? -1 : undefined}\n />\n {/* The container no longer has its own p-2, so this inset — and the\n mt-auto that anchors the button to the bottom as the textarea\n grows — lives on a wrapper instead of the button's own\n className: a host-supplied SendButton can (and, per our own\n stories, does) spread its own className over the one we pass in,\n which would otherwise silently drop this spacing. The sticky is\n here for the same reason.\n\n sticky: mt-auto alone anchors the button to the bottom of a row the\n textarea can grow taller than the card, carrying send out of the\n scroll's view while you are still typing. Sticking it to the\n scrollport keeps it reachable without a scroll, floating over the\n textarea's own column, so nothing reflows and no text runs under it.\n The offset matches mb-2 because sticky resolves against the border\n box, not the margin box: at bottom-0 the button would drop that\n inset and sit flush with the card's edge for as long as it stayed\n stuck. A sticky element cannot leave its containing block, which is\n the row — so scrolling all the way up to the previews takes the\n button off-screen with the row it belongs to, and it is pinned at\n every position where the row is in view, i.e. whenever you are\n typing.\n\n pt-2 gives the button the same inset above it that mb-2 gives below,\n and keeps it there while the card scrolls: sticky offsets only the\n bottom edge, so once the card scrolls far enough for the button to\n ride up to the top of the row, a staged preview would otherwise meet\n it flush. The padding travels with the button, so the gap holds at\n every scroll position rather than only at rest. It needs no\n background to sit on: sticky cannot leave its containing block, so\n the button stays inside the row and a preview never passes behind\n it. */}\n <div className=\"sticky bottom-2 mt-auto mb-2 mr-2 shrink-0 pt-2\">\n <SendButton\n sendMessage={handleSubmit}\n aria-label=\"Send\"\n // before:-inset-2 extends the button's clickable area to 48x48\n // (MES-1325) via an invisible pseudo-element — clicks on it still\n // land on the button itself — without growing the 32x32 circle\n // `size-8`/`rounded-full`/`bg-[#121110]` visually describe.\n className=\"str-chat__send-button relative flex justify-center items-center rounded-full size-8 bg-[#121110] disabled:bg-[#F1F0EE] disabled:text-black/20 text-white focus-ring before:absolute before:-inset-2 before:content-['']\"\n data-testid=\"send-button\"\n disabled={isSendDisabled}\n type=\"button\"\n />\n </div>\n </div>\n </div>\n )\n}\n\nexport interface CustomMessageInputProps {\n renderActions?: () => React.ReactNode\n /**\n * Content rendered above the composer controls (and above the locked panel\n * when `disabled`). Used by the floating layout to keep conversation footer\n * content inside the absolutely positioned chrome so it is not covered.\n */\n renderHeader?: () => React.ReactNode\n renderFooter?: () => React.ReactNode\n /**\n * Replace the composer entirely with a non-interactive locked panel that\n * shows `disabledReason`. Used by the Linktree official channel, where the\n * linker cannot message Linktree from the inbox. Defaults to false.\n *\n * Distinct from the channel's `frozen` flag, which keeps the composer\n * rendered but read-only/dimmed.\n */\n disabled?: boolean\n /**\n * Explanatory text shown inside the locked panel. Only rendered when\n * `disabled` is true.\n */\n disabledReason?: string\n /**\n * Replace the inner composer — the textarea + send arrangement rendered\n * inside Stream's `<MessageInput>` — with a host-owned component. When\n * provided it is passed to Stream as the `Input` component, so it runs inside\n * the message-input context and can drive sending, attachments, and previews\n * via the Stream hooks (`useMessageInputContext`,\n * `useMessageComposerHasSendableData`, `TextareaComposer`, …). The outer\n * frozen/`renderActions`/`renderFooter` wrapper is unchanged; read\n * `useComposerLocked()` to honour the frozen state. Defaults to the built-in\n * composer.\n */\n composerInput?: React.ComponentType\n}\n\nexport const CustomMessageInput: React.FC<CustomMessageInputProps> = ({\n renderActions,\n renderHeader,\n renderFooter,\n disabled = false,\n disabledReason,\n composerInput,\n}) => {\n const { channel } = useChannelStateContext()\n const isFrozen = channel?.data?.frozen === true\n const isFloating = Boolean(composerInput)\n const composerRef = React.useRef<HTMLDivElement>(null)\n\n React.useEffect(() => {\n if (!isFloating) {\n return\n }\n\n const composer = composerRef.current\n const layout = composer?.parentElement\n\n if (!composer || !layout) {\n return\n }\n\n // Track the composer height across resizes so we can re-pin only when it\n // actually grows (see below).\n let previousComposerHeight = composer.offsetHeight\n\n const updateComposerHeight = () => {\n // The floating composer overlays the message list; growing it only\n // reserves space via padding-bottom, which never moves the viewport, so\n // a taller composer creeps up over the last message (MES-1303). Capture\n // whether the thread is pinned to the latest message *before* the padding\n // grows, then re-pin afterwards.\n const nextComposerHeight = composer.offsetHeight\n // Only growth can cover the latest message: shrinks free up space and\n // width-only resizes (window/layout changes) cover nothing, so re-pinning\n // then would needlessly yank a user resting just off the bottom.\n const grew = nextComposerHeight > previousComposerHeight\n previousComposerHeight = nextComposerHeight\n\n // The scroll viewport is Stream's `.str-chat__list` (overflow-y: auto);\n // its child `.str-chat__message-list-scroll` holds the padding but has\n // overflow: visible and never scrolls. The redesigned empty state also\n // renders a placeholder `.str-chat__list`, so select the one that is\n // actually scrollable rather than the first match — that is also the only\n // one where re-pinning can do anything.\n const scroller =\n Array.from(\n layout.querySelectorAll<HTMLElement>('.str-chat__list')\n ).find((el) => el.scrollHeight > el.clientHeight) ?? null\n // Guarded on `wasAtBottom` so we never yank a user reading history.\n const wasAtBottom = scroller\n ? scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight <\n NEAR_BOTTOM_THRESHOLD_PX\n : false\n\n layout.style.setProperty(\n '--messaging-composer-height',\n `${nextComposerHeight}px`\n )\n\n // Reading scrollHeight forces a synchronous reflow, so it reflects the\n // padding-bottom we just set; scrolling to it pins the true bottom.\n if (grew && wasAtBottom && scroller) {\n scroller.scrollTop = scroller.scrollHeight\n }\n }\n\n updateComposerHeight()\n\n if (typeof ResizeObserver === 'undefined') {\n return\n }\n\n const resizeObserver = new ResizeObserver(updateComposerHeight)\n resizeObserver.observe(composer)\n\n return () => {\n resizeObserver.disconnect()\n layout.style.removeProperty('--messaging-composer-height')\n }\n }, [isFloating])\n\n // Linktree official channel: the composer is replaced by an info panel\n // explaining the linker cannot send messages on this thread.\n if (disabled) {\n if (!isFloating) {\n return (\n <>\n {renderHeader?.()}\n <div className=\"messaging-composer-locked-panel flex w-full flex-col items-center justify-center gap-3 px-6 py-4\">\n {disabledReason ? (\n <p className=\"max-w-[345px] text-center text-xs font-normal leading-[1.3] tracking-[0.12px] text-black/40\">\n {disabledReason}\n </p>\n ) : null}\n </div>\n {renderFooter?.()}\n </>\n )\n }\n\n return (\n <div\n ref={composerRef}\n className=\"messaging-composer-chrome messaging-composer-chrome--floating\"\n >\n <div className=\"messaging-composer-backdrop\" aria-hidden=\"true\">\n <div className=\"messaging-composer-backdrop-blur\" />\n <div className=\"messaging-composer-backdrop-blur\" />\n <div className=\"messaging-composer-backdrop-blur\" />\n <div className=\"messaging-composer-backdrop-blur\" />\n <div className=\"messaging-composer-backdrop-blur\" />\n <div className=\"messaging-composer-backdrop-gradient\" />\n </div>\n <div className=\"relative z-10 flex flex-col\">\n {renderHeader?.()}\n <div className=\"messaging-composer-locked-panel flex w-full flex-col items-center justify-center gap-3 px-6 py-4\">\n {disabledReason ? (\n <p className=\"max-w-[345px] text-center text-xs font-normal leading-[1.3] tracking-[0.12px] text-black/40\">\n {disabledReason}\n </p>\n ) : null}\n </div>\n {renderFooter?.()}\n </div>\n </div>\n )\n }\n\n if (!isFloating) {\n return (\n <div className=\"flex min-h-0 flex-col gap-4 p-4\">\n {renderHeader?.()}\n <div\n // @ts-expect-error Only React 19 onwards has `inert` in its types.\n inert={isFrozen ? '' : undefined}\n aria-disabled={isFrozen || undefined}\n className=\"message-input flex min-h-0 items-end gap-4 aria-disabled:opacity-40\"\n >\n {renderActions && (\n <div className=\"flex h-12 shrink-0 items-center justify-center\">\n {renderActions()}\n </div>\n )}\n <ComposerLockedContext.Provider value={isFrozen}>\n <MessageInput Input={CustomMessageInputInner} />\n </ComposerLockedContext.Provider>\n </div>\n {renderFooter?.()}\n </div>\n )\n }\n\n return (\n <div\n ref={composerRef}\n className=\"messaging-composer-chrome messaging-composer-chrome--floating\"\n >\n <div className=\"messaging-composer-backdrop\" aria-hidden=\"true\">\n <div className=\"messaging-composer-backdrop-blur\" />\n <div className=\"messaging-composer-backdrop-blur\" />\n <div className=\"messaging-composer-backdrop-blur\" />\n <div className=\"messaging-composer-backdrop-blur\" />\n <div className=\"messaging-composer-backdrop-blur\" />\n <div className=\"messaging-composer-backdrop-gradient\" />\n </div>\n\n <div className=\"relative z-10 flex min-h-0 flex-col\">\n {renderHeader?.()}\n <div className=\"flex min-h-0 flex-col gap-4 p-4\">\n <div\n // @ts-expect-error Only React 19 onwards has `inert` in its types.\n inert={isFrozen ? '' : undefined}\n aria-disabled={isFrozen || undefined}\n className=\"message-input flex min-h-0 items-end gap-4 aria-disabled:opacity-40\"\n >\n {renderActions && (\n <div className=\"flex h-12 shrink-0 items-center justify-center\">\n {renderActions()}\n </div>\n )}\n <ComposerLockedContext.Provider value={isFrozen}>\n <MessageInput Input={composerInput ?? CustomMessageInputInner} />\n </ComposerLockedContext.Provider>\n </div>\n {renderFooter?.()}\n </div>\n </div>\n </div>\n )\n}\n","import {\n isAgeSafetySystemType,\n isDmAgentSystemType,\n type AgeSafetySystemType,\n type DmAgentSystemType,\n} from '@linktr.ee/messaging-taxonomy'\nimport { ProhibitIcon, SparkleIcon } from '@phosphor-icons/react'\nimport { MessageTimestamp, type EventComponentProps } from 'stream-chat-react'\n\nconst DM_AGENT_SYSTEM_MESSAGE_FALLBACK_TEXT: Record<DmAgentSystemType, string> =\n {\n SYSTEM_DM_AGENT_PAUSED: 'DM Agent has left the conversation',\n SYSTEM_DM_AGENT_RESUMED: 'DM Agent has rejoined the conversation',\n }\n\n/**\n * System-message timestamps are hidden by default. A semantic type must opt in\n * here to render one, so new/unknown system types stay timestamp-less until\n * explicitly added. Keep in sync with the mobile client (cerberus\n * `CustomMessageSystem`); there is no shared package between the two surfaces.\n */\nexport const SHOW_SYSTEM_MESSAGE_TIMESTAMP_TYPES: readonly AgeSafetySystemType[] =\n ['SYSTEM_AGE_SAFETY_BLOCKED']\n\nconst AGE_SAFETY_SYSTEM_MESSAGE_FALLBACK_TEXT: Record<\n AgeSafetySystemType,\n string\n> = {\n SYSTEM_AGE_SAFETY_BLOCKED:\n 'This user isn’t able to reply because they don’t meet our age safety guidelines.',\n}\n\nconst AGE_SAFETY_SYSTEM_MESSAGE_EMPHASIS = 'age safety guidelines.'\nconst AGE_SAFETY_SYSTEM_MESSAGE_URL = 'https://linktr.ee/s/about/contact'\n\nconst isShowTimestampSystemType = (\n value: string | undefined\n): value is (typeof SHOW_SYSTEM_MESSAGE_TIMESTAMP_TYPES)[number] => {\n return SHOW_SYSTEM_MESSAGE_TIMESTAMP_TYPES.includes(\n value as (typeof SHOW_SYSTEM_MESSAGE_TIMESTAMP_TYPES)[number]\n )\n}\n\nconst shouldShowSystemMessageTimestamp = (\n message: EventComponentProps['message']\n): boolean => {\n return isShowTimestampSystemType(message.metadata?.custom_type)\n}\n\ntype CustomSystemMessageVariant =\n | {\n kind: 'dm-agent'\n type: DmAgentSystemType\n }\n | {\n kind: 'age-safety'\n type: AgeSafetySystemType\n }\n\nconst getCustomSystemMessageVariant = (\n message: EventComponentProps['message']\n): CustomSystemMessageVariant | undefined => {\n const metadataCustomType = message.metadata?.custom_type\n if (isDmAgentSystemType(metadataCustomType)) {\n return {\n kind: 'dm-agent',\n type: metadataCustomType,\n }\n }\n\n if (isAgeSafetySystemType(metadataCustomType)) {\n return {\n kind: 'age-safety',\n type: metadataCustomType,\n }\n }\n\n const fallbackType = message.dm_agent_system_type\n if (isDmAgentSystemType(fallbackType)) {\n return {\n kind: 'dm-agent',\n type: fallbackType,\n }\n }\n\n return undefined\n}\n\nconst renderAgeSafetyMessageText = (messageText: string): React.ReactNode => {\n const emphasisIndex = messageText.indexOf(AGE_SAFETY_SYSTEM_MESSAGE_EMPHASIS)\n if (emphasisIndex === -1) {\n return messageText\n }\n\n const emphasisEndIndex =\n emphasisIndex + AGE_SAFETY_SYSTEM_MESSAGE_EMPHASIS.length\n\n return (\n <>\n {messageText.slice(0, emphasisIndex)}\n <a\n href={AGE_SAFETY_SYSTEM_MESSAGE_URL}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"mes-age-safety-system-message__emphasis font-medium text-inherit underline\"\n >\n {AGE_SAFETY_SYSTEM_MESSAGE_EMPHASIS}\n </a>\n {messageText.slice(emphasisEndIndex)}\n </>\n )\n}\n\nexport const CustomSystemMessage: React.FC<EventComponentProps> = (props) => {\n const isDateHidden = !shouldShowSystemMessageTimestamp(props.message)\n const customSystemMessageVariant = getCustomSystemMessageVariant(\n props.message\n )\n\n if (customSystemMessageVariant?.kind === 'dm-agent') {\n const messageText =\n props.message.text?.trim() ||\n DM_AGENT_SYSTEM_MESSAGE_FALLBACK_TEXT[customSystemMessageVariant.type]\n\n return (\n <div className=\"str-chat__message--system\" data-testid=\"message-system\">\n <div\n className=\"mes-dm-agent-system-message mx-auto mb-2 inline-flex w-fit max-w-[min(100%,480px)] items-center justify-center gap-[10px] rounded-[12px] border border-[rgba(0,0,0,0.08)] p-3 text-[rgba(0,0,0,0.55)]\"\n data-testid=\"dm-agent-system-message\"\n data-dm-agent-system-type={customSystemMessageVariant.type}\n >\n <SparkleIcon\n size={16}\n weight=\"regular\"\n aria-hidden\n className=\"mes-dm-agent-system-message__sparkle shrink-0\"\n />\n <p className=\"mes-dm-agent-system-message__text m-0 text-center text-[14px] font-normal leading-5 tracking-[0.21px]\">\n {messageText}\n </p>\n </div>\n {!isDateHidden && <MessageTimestamp message={props.message} />}\n </div>\n )\n }\n\n if (customSystemMessageVariant?.kind === 'age-safety') {\n const messageText =\n props.message.text?.trim() ||\n AGE_SAFETY_SYSTEM_MESSAGE_FALLBACK_TEXT[customSystemMessageVariant.type]\n\n return (\n <div className=\"str-chat__message--system\" data-testid=\"message-system\">\n <div\n className=\"mes-age-safety-system-message box-border mx-auto mb-2 flex w-full max-w-[329px] items-start justify-center gap-3 rounded-[12px] border border-[var(--border-secondary,rgba(0,0,0,0.08))] bg-[var(--bg-warning-subtle,#fef3c6)] px-2 py-4 pl-5 text-[color:var(--text-warning-on-warning,#894b00)]\"\n data-testid=\"age-safety-system-message\"\n data-age-safety-system-type={customSystemMessageVariant.type}\n >\n <ProhibitIcon\n size={24}\n weight=\"duotone\"\n aria-hidden\n className=\"mes-age-safety-system-message__icon shrink-0 text-[color:var(--text-warning-on-warning,#894b00)]\"\n data-testid=\"age-safety-system-message-icon\"\n />\n <div className=\"mes-age-safety-system-message__content min-w-0 flex-[1_0_0]\">\n <p className=\"m-0 text-balance text-left text-[12px] font-normal leading-4 tracking-[0.21px] text-[color:var(--text-warning-on-warning,#894b00)]\">\n {renderAgeSafetyMessageText(messageText)}\n </p>\n </div>\n </div>\n {!isDateHidden && <MessageTimestamp message={props.message} />}\n </div>\n )\n }\n\n return (\n <div className=\"str-chat__message--system\" data-testid=\"message-system\">\n <div className=\"str-chat__message--system__text\">\n <div className=\"str-chat__message--system__line\"></div>\n <p>{props.message.text}</p>\n <div className=\"str-chat__message--system__line\"></div>\n </div>\n {!isDateHidden && <MessageTimestamp message={props.message} />}\n </div>\n )\n}\n","import { createContext } from 'react'\n\nexport const DmAgentEnabledContext = createContext<boolean>(false)\n","import React, { useContext } from 'react'\nimport type { Event, UserResponse } from 'stream-chat'\nimport {\n AIStates,\n useAIState,\n useChannelStateContext,\n useChatContext,\n useTypingContext,\n} from 'stream-chat-react'\n\nimport { Avatar } from '../Avatar'\n\nimport { DmAgentEnabledContext } from './DmAgentContext'\n\ninterface CustomTypingIndicatorProps {\n threadList?: boolean\n}\n\nconst Circle = ({ cx, index }: { cx: string; index: number }) => (\n <circle cx={cx} cy=\"6.15\" r=\"3.9\" fill=\"#A0A0A0\">\n <animateTransform\n attributeName=\"transform\"\n type=\"translate\"\n values=\"0 0; 0 -2.25; 0 0;\"\n dur=\"900ms\"\n begin={`${120 * index}ms`} // 0ms, 120ms, 240ms\n repeatCount=\"indefinite\"\n />\n </circle>\n)\n\nconst AI_ACTIVE_STATES = new Set<string>([\n AIStates.Thinking,\n AIStates.Generating,\n AIStates.ExternalSources,\n])\n\nconst CustomTypingIndicator = ({ threadList }: CustomTypingIndicatorProps) => {\n const { channel, channelConfig, thread } = useChannelStateContext()\n const { client } = useChatContext()\n const { typing = {} } = useTypingContext()\n const { aiState } = useAIState(channel)\n const dmAgentEnabled = useContext(DmAgentEnabledContext)\n\n // Show the AI indicator whenever the consumer agent is producing a reply.\n // This event stream is independent of `typing.start`/`typing.stop`, so it is\n // intentionally NOT gated by `channelConfig.typing_events`. Gate strictly on\n // `dmAgentEnabled` so stale or off-surface ai_indicator events never surface\n // the bubble on channels where the agent is not active.\n const isAiActive =\n !threadList && dmAgentEnabled && AI_ACTIVE_STATES.has(aiState)\n\n if (isAiActive) {\n const agentUser = findOtherChannelUser(channel, client.user?.id)\n return (\n <TypingBubble\n avatarId={agentUser?.id ?? 'ai-agent'}\n avatarName={agentUser?.name ?? agentUser?.id ?? 'Agent'}\n avatarImage={agentUser?.image}\n testId=\"typing-indicator-ai\"\n />\n )\n }\n\n if (channelConfig?.typing_events === false) {\n return null\n }\n\n const typingInChannel = !threadList\n ? Object.values(typing).filter(\n ({ parent_id, user }: Event) =>\n user?.id !== client.user?.id && !parent_id\n )\n : []\n\n const typingInThread = threadList\n ? Object.values(typing).filter(\n ({ parent_id, user }: Event) =>\n user?.id !== client.user?.id && parent_id === thread?.id\n )\n : []\n\n const typingUsers = threadList ? typingInThread : typingInChannel\n if (!typingUsers.length) {\n return null\n }\n\n const typingUser = typingUsers[0]?.user\n const memberUser =\n typingUser?.id && channel.state.members[typingUser.id]\n ? channel.state.members[typingUser.id].user\n : undefined\n\n return (\n <TypingBubble\n avatarId={typingUser?.id ?? memberUser?.id ?? 'typing-user'}\n avatarName={\n typingUser?.name ?? memberUser?.name ?? typingUser?.id ?? 'Typing user'\n }\n avatarImage={typingUser?.image ?? memberUser?.image}\n testId=\"typing-indicator\"\n />\n )\n}\n\nconst TypingBubble = ({\n avatarId,\n avatarName,\n avatarImage,\n testId,\n}: {\n avatarId: string\n avatarName: string\n avatarImage?: string | null\n testId: string\n}) => (\n <div\n className=\"str-chat__li str-chat__li--single str-chat__typing-indicator !static\"\n data-testid={testId}\n style={{\n marginBottom: 'var(--messaging-channel-input-spacer-height, 80px)',\n }}\n >\n <div className=\"str-chat__message str-chat__message-simple str-chat__message--regular str-chat__message--received str-chat__message--other str-chat__message--has-text\">\n <Avatar\n className=\"str-chat__avatar str-chat__message-sender-avatar\"\n id={avatarId}\n name={avatarName}\n image={avatarImage ?? undefined}\n size={24}\n shape=\"circle\"\n />\n <div className=\"str-chat__message-inner mx-0\">\n <div className=\"str-chat__message-bubble-wrapper\">\n <div className=\"str-chat__message-bubble\">\n <div className=\"str-chat__message-text min-h-11 flex items-center\">\n <div className=\"str-chat__message-text-inner str-chat__message-simple-text-inner\">\n <svg\n aria-hidden=\"true\"\n className=\"block overflow-visible\"\n viewBox=\"0 0 32 12.3\"\n width=\"32\"\n height=\"13\"\n overflow=\"visible\"\n >\n <Circle cx=\"4\" index={0} />\n <Circle cx=\"16\" index={1} />\n <Circle cx=\"28\" index={2} />\n </svg>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n </div>\n)\n\ntype ChannelLike = ReturnType<typeof useChannelStateContext>['channel']\n\nfunction findOtherChannelUser(\n channel: ChannelLike,\n selfId: string | undefined\n): UserResponse | undefined {\n const members = channel?.state?.members ?? {}\n for (const member of Object.values(members)) {\n const memberUser = member?.user\n if (memberUser && memberUser.id !== selfId) {\n return memberUser\n }\n }\n return undefined\n}\n\nexport default CustomTypingIndicator\n","import React from 'react'\n\n/**\n * Empty state component shown when a channel has no messages\n * Returns null to show nothing - the LoadingIndicator handles the loading state\n */\nexport const ChannelEmptyState: React.FC = () => null\n","import classNames from 'classnames'\n\ntype LoadingProps = {\n className?: string\n message?: string\n}\n\nconst Loading = ({ className, message }: LoadingProps) => (\n <div\n className={classNames('flex items-center justify-center h-full', className)}\n >\n <svg viewBox=\"0 0 100 100\" className=\"size-8 fill-pebble\" stroke=\"none\">\n <circle cx=\"6\" cy=\"50\" r=\"6\">\n <animateTransform\n attributeName=\"transform\"\n dur=\"1s\"\n type=\"translate\"\n values=\"0 15 ; 0 -15; 0 15\"\n repeatCount=\"indefinite\"\n begin=\"0.1\"\n />\n </circle>\n <circle cx=\"30\" cy=\"50\" r=\"6\">\n <animateTransform\n attributeName=\"transform\"\n dur=\"1s\"\n type=\"translate\"\n values=\"0 10 ; 0 -10; 0 10\"\n repeatCount=\"indefinite\"\n begin=\"0.2\"\n />\n </circle>\n <circle cx=\"54\" cy=\"50\" r=\"6\">\n <animateTransform\n attributeName=\"transform\"\n dur=\"1s\"\n type=\"translate\"\n values=\"0 5 ; 0 -5; 0 5\"\n repeatCount=\"indefinite\"\n begin=\"0.3\"\n />\n </circle>\n </svg>\n {message && <span className=\"text-stone\">{message}</span>}\n </div>\n)\n\nexport default Loading\n","import React from 'react'\n\nimport Loading from '../Loading'\n\n/**\n * Loading state component\n */\nexport const LoadingState = React.memo(() => (\n <div className=\"messaging-loading-state flex items-center justify-center h-full\">\n <div className=\"flex items-center\">\n <Loading className=\"w-6 h-6\" />\n <span className=\"text-sm text-stone\">Loading messages</span>\n </div>\n </div>\n))\nLoadingState.displayName = 'LoadingState'\n","import classNames from 'classnames'\nimport React, { useCallback, useEffect, useRef } from 'react'\nimport { Channel as ChannelType } from 'stream-chat'\nimport {\n Channel,\n Window,\n MessageList,\n useMessageContext,\n useChannelStateContext,\n WithComponents,\n MessageUIComponentProps,\n} from 'stream-chat-react'\n\nimport type { ChannelViewProps } from '../types'\n\nimport { ChannelHeaderRedesign } from './ChannelHeaderRedesign'\nimport { CustomDateSeparator } from './CustomDateSeparator'\nimport { CustomMessage } from './CustomMessage'\nimport {\n BroadcastNameContext,\n useBroadcastNamesById,\n} from './CustomMessage/BroadcastNameContext'\nimport { CustomMessageActions } from './CustomMessage/CustomMessageActions'\nimport { CustomMessageInput } from './CustomMessageInput'\nimport { CustomSystemMessage } from './CustomSystemMessage'\nimport CustomTypingIndicator from './CustomTypingIndicator'\nimport { DmAgentEnabledContext } from './CustomTypingIndicator/DmAgentContext'\nimport { ChannelEmptyState } from './MessagingShell/ChannelEmptyState'\nimport { LoadingState } from './MessagingShell/LoadingState'\n\n/**\n * Inner component that has access to channel context\n */\nconst ChannelViewInner: React.FC<{\n onBack?: () => void\n showBackButton: boolean\n renderMessageInputActions?: (channel: ChannelType) => React.ReactNode\n renderMessageInputFooter?: (channel: ChannelType) => React.ReactNode\n renderConversationFooter?: (channel: ChannelType) => React.ReactNode\n onLeaveConversation?: (channel: ChannelType) => void\n onBlockParticipant?: (participantId?: string) => void\n CustomChannelEmptyState?: React.ComponentType\n showDeleteConversation?: boolean\n onDeleteConversationClick?: () => void\n onBlockParticipantClick?: () => void\n onReportParticipantClick?: () => void\n showBlockParticipant?: boolean\n showReportParticipant?: boolean\n composerDisabled?: boolean\n composerDisabledReason?: string\n showStarButton?: boolean\n chatbotVotingEnabled?: boolean\n renderChannelBanner?: () => React.ReactNode\n customChannelActions?: React.ReactNode\n renderChannelActions?: React.ReactNode\n renderMessage?: (\n messageNode: React.ReactElement,\n message: NonNullable<MessageUIComponentProps['message']>\n ) => React.ReactNode\n viewerLanguage?: string\n showChannelInfo?: boolean\n onParticipantNameClick?: () => void\n onBroadcastClick?: ChannelViewProps['onBroadcastClick']\n resolveBroadcastName?: ChannelViewProps['resolveBroadcastName']\n composerInput?: ChannelViewProps['composerInput']\n}> = ({\n onBack,\n showBackButton,\n renderMessageInputActions,\n renderMessageInputFooter,\n renderConversationFooter,\n onLeaveConversation,\n onBlockParticipant,\n showDeleteConversation = true,\n onDeleteConversationClick,\n onBlockParticipantClick,\n onReportParticipantClick,\n showBlockParticipant = true,\n showReportParticipant = true,\n composerDisabled = false,\n composerDisabledReason,\n showStarButton = false,\n chatbotVotingEnabled = false,\n renderChannelBanner,\n customChannelActions,\n renderChannelActions,\n renderMessage,\n viewerLanguage,\n showChannelInfo = true,\n onParticipantNameClick,\n onBroadcastClick,\n resolveBroadcastName,\n composerInput,\n}) => {\n const { channel, messages } = useChannelStateContext()\n const onBroadcastClickRef = useRef(onBroadcastClick)\n useEffect(() => {\n onBroadcastClickRef.current = onBroadcastClick\n }, [onBroadcastClick])\n const stableOnBroadcastClick = useCallback<\n NonNullable<ChannelViewProps['onBroadcastClick']>\n >((broadcast) => {\n onBroadcastClickRef.current?.(broadcast)\n }, [])\n const messageBroadcastClick = onBroadcastClick\n ? stableOnBroadcastClick\n : undefined\n // Resolve outside memoized Stream rows so new campaign names publish without\n // remounting the rows.\n const broadcastNamesById = useBroadcastNamesById(\n messages,\n resolveBroadcastName\n )\n\n const hasVisibleMessages = channel.state.messages.some(\n (message) => message.type !== 'deleted'\n )\n const channelBanner = renderChannelBanner ? (\n <React.Fragment key=\"lt-channel-banner\">\n {renderChannelBanner()}\n </React.Fragment>\n ) : null\n const channelHeader = (\n <ChannelHeaderRedesign\n className=\"-mx-[var(--str-chat\\_\\_spacing-2)] px-[var(--str-chat\\_\\_spacing-2)] md:-mx-[min(var(--str-chat\\_\\_spacing-10),4%)] md:px-[min(var(--str-chat\\_\\_spacing-10),4%)]\"\n onBack={onBack}\n showBackButton={showBackButton}\n showChannelInfo={showChannelInfo}\n showStarButton={showStarButton}\n onLeaveConversation={onLeaveConversation}\n onBlockParticipant={onBlockParticipant}\n showDeleteConversation={showDeleteConversation}\n showBlockParticipant={showBlockParticipant}\n showReportParticipant={showReportParticipant}\n onDeleteConversationClick={onDeleteConversationClick}\n onBlockParticipantClick={onBlockParticipantClick}\n onReportParticipantClick={onReportParticipantClick}\n customChannelActions={customChannelActions}\n renderChannelActions={renderChannelActions}\n onParticipantNameClick={onParticipantNameClick}\n />\n )\n\n // Prevents all message instances from unmounting when ChannelViewInner re-renders\n const MessageOverride = useCallback(\n (props: MessageUIComponentProps) => {\n // eslint-disable-next-line react-hooks/rules-of-hooks\n const { message } = useMessageContext('ChannelView')\n const messageNode = (\n <CustomMessage\n {...props}\n chatbotVotingEnabled={chatbotVotingEnabled}\n viewerLanguage={viewerLanguage}\n onBroadcastClick={messageBroadcastClick}\n />\n )\n\n if (!renderMessage || !message) {\n return messageNode\n }\n\n return renderMessage(messageNode, message)\n },\n [chatbotVotingEnabled, messageBroadcastClick, renderMessage, viewerLanguage]\n )\n\n return (\n <BroadcastNameContext.Provider value={broadcastNamesById}>\n <WithComponents\n overrides={{\n Message: MessageOverride,\n MessageActions: CustomMessageActions,\n UnreadMessagesNotification: () => null,\n UnreadMessagesSeparator: () => null,\n }}\n >\n <Window>\n {composerInput ? (\n <div className=\"messaging-channel-layout relative flex min-h-0 flex-1 flex-col\">\n {/* Message List */}\n <div\n key=\"lt-channel-message-list\"\n className=\"messaging-channel-message-list messaging-channel-message-list--floating relative min-h-0 flex-1 overflow-hidden\"\n >\n {/* Stream only renders MessageList.head when there are messages, so\n empty channels need the header and banner in normal flow. */}\n {!hasVisibleMessages && (\n <div className=\"str-chat__list\">\n <div className=\"str-chat__message-list-scroll\">\n {channelHeader}\n {channelBanner}\n </div>\n </div>\n )}\n\n <MessageList\n head={\n hasVisibleMessages ? (\n <div className=\"sticky top-0 z-10\">\n {channelHeader}\n {channelBanner}\n </div>\n ) : undefined\n }\n hideDeletedMessages\n hideNewMessageSeparator={false}\n />\n </div>\n\n {/* Message Input — conversation footer is rendered inside the\n floating chrome (via renderHeader) so it stays above the absolute\n composer and remains interactive. */}\n <CustomMessageInput\n key=\"lt-channel-message-input\"\n {...(renderMessageInputActions && {\n renderActions: () => renderMessageInputActions?.(channel),\n })}\n {...(renderConversationFooter && {\n renderHeader: () => renderConversationFooter(channel),\n })}\n renderFooter={() => renderMessageInputFooter?.(channel)}\n disabled={composerDisabled}\n disabledReason={composerDisabledReason}\n composerInput={composerInput}\n />\n </div>\n ) : (\n <>\n {/* Message List */}\n <div\n key=\"lt-channel-message-list\"\n className=\"flex-1 overflow-hidden relative\"\n >\n {/* Stream only renders MessageList.head when there are messages, so\n empty channels need the header and banner in normal flow. */}\n {!hasVisibleMessages && (\n <div className=\"str-chat__list\">\n <div className=\"str-chat__message-list-scroll\">\n {channelHeader}\n {channelBanner}\n </div>\n </div>\n )}\n\n <MessageList\n head={\n hasVisibleMessages ? (\n <div className=\"sticky top-0 z-10\">\n {channelHeader}\n {channelBanner}\n </div>\n ) : undefined\n }\n hideDeletedMessages\n hideNewMessageSeparator={false}\n />\n </div>\n\n {renderConversationFooter ? (\n <React.Fragment key=\"lt-channel-conversation-footer\">\n {renderConversationFooter(channel)}\n </React.Fragment>\n ) : null}\n\n {/* Message Input */}\n <CustomMessageInput\n key=\"lt-channel-message-input\"\n {...(renderMessageInputActions && {\n renderActions: () => renderMessageInputActions?.(channel),\n })}\n renderFooter={() => renderMessageInputFooter?.(channel)}\n disabled={composerDisabled}\n disabledReason={composerDisabledReason}\n composerInput={composerInput}\n />\n </>\n )}\n </Window>\n </WithComponents>\n </BroadcastNameContext.Provider>\n )\n}\n\n/**\n * Channel view component with message list and input\n */\nexport const ChannelView = React.memo<ChannelViewProps>(\n ({\n channel,\n onBack,\n showBackButton = false,\n renderMessageInputActions,\n renderMessageInputFooter,\n renderConversationFooter,\n onLeaveConversation,\n onBlockParticipant,\n className,\n CustomChannelEmptyState = ChannelEmptyState,\n showDeleteConversation = true,\n onDeleteConversationClick,\n onBlockParticipantClick,\n onReportParticipantClick,\n showBlockParticipant = true,\n showReportParticipant = true,\n composerDisabled = false,\n composerDisabledReason,\n dmAgentEnabled,\n messageMetadata,\n onMessageSent,\n showStarButton = false,\n chatbotVotingEnabled = false,\n renderChannelBanner,\n customChannelActions,\n renderChannelActions,\n renderMessage,\n onMessageLinkClick,\n sendButton,\n attachmentPreviewList,\n viewerLanguage,\n showChannelInfo = true,\n onParticipantNameClick,\n onBroadcastClick,\n resolveBroadcastName,\n composerInput,\n }) => {\n // Custom send message handler that:\n // 1. Applies messageMetadata if provided\n // 2. Adds skip_push and silent when DM agent is active\n // 3. Calls onMessageSent callback with full response\n // Read chatbot_paused inside callback to get current value at send time (not stale closure)\n const doSendMessageRequest = useCallback(\n async (\n _channel: ChannelType,\n message: Parameters<ChannelType['sendMessage']>[0],\n options?: Parameters<ChannelType['sendMessage']>[1]\n ) => {\n const agentPaused = channel.data?.chatbot_paused === true\n const shouldSuppressNotifications = dmAgentEnabled && !agentPaused\n\n // Build final message with nested metadata\n const finalMessage = {\n ...message,\n ...(shouldSuppressNotifications && { silent: true }),\n ...(messageMetadata && {\n metadata: {\n ...(message.metadata ?? {}),\n ...messageMetadata,\n },\n }),\n }\n\n // Build final options\n const finalOptions = {\n ...options,\n ...(shouldSuppressNotifications && { skip_push: true }),\n }\n\n const response = await channel.sendMessage(finalMessage, finalOptions)\n\n // Fire callback with full response (includes message.id)\n onMessageSent?.(response)\n\n return response\n },\n [channel, dmAgentEnabled, messageMetadata, onMessageSent]\n )\n\n const containerRef = useRef<HTMLDivElement>(null)\n useEffect(() => {\n if (!onMessageLinkClick) return\n const container = containerRef.current\n if (!container) return\n const handler = (event: MouseEvent) => {\n const target = event.target as HTMLElement | null\n const anchor = target?.closest('a[href]')\n const href = anchor?.getAttribute('href')\n if (!href) return\n const messageWrapper = anchor?.closest<HTMLElement>('[data-message-id]')\n const messageId = messageWrapper?.getAttribute('data-message-id')\n const message = messageId\n ? channel.state.messages.find((m) => m.id === messageId)\n : undefined\n onMessageLinkClick(href, message)\n }\n container.addEventListener('click', handler)\n return () => container.removeEventListener('click', handler)\n }, [onMessageLinkClick, channel])\n\n return (\n <div\n ref={containerRef}\n className={classNames(\n 'messaging-channel-view h-full flex flex-col',\n className\n )}\n >\n <DmAgentEnabledContext.Provider value={dmAgentEnabled ?? false}>\n <Channel\n channel={channel}\n MessageSystem={CustomSystemMessage}\n EmptyStateIndicator={CustomChannelEmptyState}\n LoadingIndicator={LoadingState}\n DateSeparator={CustomDateSeparator}\n TypingIndicator={CustomTypingIndicator}\n doSendMessageRequest={doSendMessageRequest}\n {...(sendButton ? { SendButton: sendButton } : {})}\n {...(attachmentPreviewList\n ? { AttachmentPreviewList: attachmentPreviewList }\n : {})}\n >\n <ChannelViewInner\n onBack={onBack}\n showBackButton={showBackButton}\n renderMessageInputActions={renderMessageInputActions}\n renderMessageInputFooter={renderMessageInputFooter}\n renderConversationFooter={renderConversationFooter}\n onLeaveConversation={onLeaveConversation}\n onBlockParticipant={onBlockParticipant}\n CustomChannelEmptyState={CustomChannelEmptyState}\n showDeleteConversation={showDeleteConversation}\n onDeleteConversationClick={onDeleteConversationClick}\n onBlockParticipantClick={onBlockParticipantClick}\n onReportParticipantClick={onReportParticipantClick}\n showBlockParticipant={showBlockParticipant}\n showReportParticipant={showReportParticipant}\n composerDisabled={composerDisabled}\n composerDisabledReason={composerDisabledReason}\n showStarButton={showStarButton}\n chatbotVotingEnabled={chatbotVotingEnabled}\n renderChannelBanner={renderChannelBanner}\n customChannelActions={customChannelActions}\n renderChannelActions={renderChannelActions}\n renderMessage={renderMessage}\n viewerLanguage={viewerLanguage}\n showChannelInfo={showChannelInfo}\n onParticipantNameClick={onParticipantNameClick}\n onBroadcastClick={onBroadcastClick}\n resolveBroadcastName={resolveBroadcastName}\n composerInput={composerInput}\n />\n </Channel>\n </DmAgentEnabledContext.Provider>\n </div>\n )\n }\n)\nChannelView.displayName = 'ChannelView'\n","import React from 'react'\n\ntype ErrorStateProps = {\n message: string\n onBack?: () => void\n}\n\n/**\n * Error state component shown when something goes wrong\n */\nexport const ErrorState = React.memo<ErrorStateProps>(({ message, onBack }) => (\n <div className=\"messaging-error-state flex items-center justify-center h-full p-8\">\n <div className=\"text-center max-w-sm\">\n <div className=\"w-24 h-24 bg-danger-alt/20 rounded-full flex items-center justify-center mx-auto mb-6\">\n <span className=\"text-4xl\">⚠️</span>\n </div>\n\n <h2 className=\"font-semibold text-charcoal mb-2\">Oops!</h2>\n\n <p className=\"text-stone text-sm mb-6\">{message}</p>\n\n {onBack && (\n <button\n type=\"button\"\n onClick={onBack}\n className=\"inline-flex items-center gap-2 px-4 py-2 text-sm font-medium text-white bg-[#7f22fe] hover:bg-primary-alt rounded-lg transition-colors focus-ring\"\n >\n Go Back\n </button>\n )}\n </div>\n </div>\n))\nErrorState.displayName = 'ErrorState'\n","/* eslint-disable react-hooks/refs -- pre-existing pattern flagged by the React\n Compiler rules added in eslint-plugin-react-hooks v7; refactoring it is\n out of scope for the lint toolchain upgrade. */\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\nimport type { Channel } from 'stream-chat'\n\nimport { useMessaging } from '../../hooks/useMessaging'\nimport type { MessagingShellProps } from '../../types'\nimport { ChannelView } from '../ChannelView'\n\nimport { ErrorState } from './ErrorState'\nimport { LoadingState } from './LoadingState'\n\n/**\n * Direct-conversation surface for one specific participant.\n *\n * Renders a single ChannelView for the channel between the connected user and\n * `initialParticipantFilter`. If no channel exists yet and\n * `initialParticipantData` is supplied, the configured StreamChatService\n * channel creator is invoked to create one.\n */\nexport const MessagingShell: React.FC<MessagingShellProps> = ({\n capabilities = {},\n renderMessageInputActions,\n renderConversationFooter,\n onChannelSelect,\n onExitConversation,\n initialParticipantFilter,\n initialParticipantData,\n CustomChannelEmptyState,\n onBlockParticipantClick,\n onReportParticipantClick,\n dmAgentEnabled,\n onMessageSent,\n chatbotVotingEnabled = false,\n viewerLanguage,\n renderChannelBanner,\n customChannelActions,\n renderChannelActions,\n onParticipantNameClick,\n onBroadcastClick,\n resolveBroadcastName,\n renderMessage,\n onMessageLinkClick,\n showChannelInfo,\n composerInput,\n}) => {\n const {\n client,\n isConnected,\n isLoading,\n error,\n refreshConnection,\n service,\n debug,\n } = useMessaging()\n\n const [selectedChannel, setSelectedChannel] = useState<Channel | null>(null)\n const [directConversationError, setDirectConversationError] = useState<\n string | null\n >(null)\n const [didExit, setDidExit] = useState(false)\n\n const { showDeleteConversation = true } = capabilities\n\n // Stash consumer props that are unstable when passed inline (object\n // literals, arrow-function callbacks) so the load effect's deps stay\n // identity-stable. Without this, the documented usage pattern in the\n // README — passing `initialParticipantData={{ ... }}` and\n // `onChannelSelect={(ch) => ...}` — re-fires the effect on every render\n // and triggers a queryChannels call each time.\n const initialParticipantDataRef = useRef(initialParticipantData)\n initialParticipantDataRef.current = initialParticipantData\n const onChannelSelectRef = useRef(onChannelSelect)\n onChannelSelectRef.current = onChannelSelect\n\n // Track the direct-conversation load to prevent repeated/concurrent loads.\n // Identity-stable deps (above) stop the common re-fire, but legitimate dep\n // changes (e.g. `service`/`client` settling during connect) can still re-run\n // this effect before the just-created channel is indexed, each firing another\n // startChannelWithParticipant call and creating duplicate welcome messages.\n const directConversationLoadRef = useRef<string | null>(null)\n\n // Mirror the currently-selected channel into a ref so the async load can read\n // the latest value rather than the (possibly stale) effect-closure value.\n const selectedChannelRef = useRef<Channel | null>(null)\n useEffect(() => {\n selectedChannelRef.current = selectedChannel\n }, [selectedChannel])\n\n useEffect(() => {\n if (!client || !isConnected) return\n\n const userId = client.userID\n if (!userId) return\n\n // Only load once per viewer ↔ participant pair. Set synchronously (before\n // the async work) so re-runs triggered by changing dependency identities\n // bail out instead of issuing another channel-create request.\n const loadKey = `${userId}::${initialParticipantFilter}`\n if (directConversationLoadRef.current === loadKey) return\n directConversationLoadRef.current = loadKey\n\n // Release the guard only if it still belongs to this load. A newer load for\n // a different pair may have taken ownership while this one was in flight, so\n // clearing unconditionally could wipe the newer guard and let a duplicate\n // load/create slip through.\n const releaseLoadGuard = () => {\n if (directConversationLoadRef.current === loadKey) {\n directConversationLoadRef.current = null\n }\n }\n\n const loadInitialChannel = async () => {\n try {\n if (debug) {\n console.log(\n '[MessagingShell] Loading initial conversation with:',\n initialParticipantFilter\n )\n }\n\n const channels = await client.queryChannels(\n {\n type: 'messaging',\n members: { $eq: [userId, initialParticipantFilter] },\n },\n {},\n { limit: 1 }\n )\n\n if (channels.length > 0) {\n setSelectedChannel(channels[0])\n setDirectConversationError(null)\n onChannelSelectRef.current?.(channels[0])\n\n if (debug) {\n console.log(\n '[MessagingShell] Initial conversation loaded:',\n channels[0].id\n )\n }\n return\n }\n\n const participantData = initialParticipantDataRef.current\n if (!participantData || !service) {\n // Allow a retry once participant data / service become available.\n releaseLoadGuard()\n setDirectConversationError('No conversation found with this account')\n if (debug) {\n console.log(\n '[MessagingShell] No conversation found for:',\n initialParticipantFilter\n )\n }\n return\n }\n\n try {\n const channel = await service.startChannelWithParticipant({\n id: participantData.id,\n name: participantData.name,\n phone: participantData.phone,\n })\n setSelectedChannel(channel)\n setDirectConversationError(null)\n onChannelSelectRef.current?.(channel)\n\n if (debug) {\n console.log(\n '[MessagingShell] Channel created and loaded:',\n channel.id\n )\n }\n } catch (createErr) {\n console.error(\n '[MessagingShell] Failed to create conversation:',\n createErr\n )\n // Allow a retry for this pair after a transient failure.\n releaseLoadGuard()\n setDirectConversationError('Failed to create conversation')\n }\n } catch (err) {\n console.error(\n '[MessagingShell] Failed to load initial conversation:',\n err\n )\n // Allow a retry for this pair after a transient failure.\n releaseLoadGuard()\n // Don't replace an already-loaded conversation with an error screen.\n // Read the latest selected channel via ref to avoid acting on a stale\n // closure value when the channel was selected mid-flight.\n if (!selectedChannelRef.current) {\n setDirectConversationError('Failed to load conversation')\n }\n }\n }\n\n void loadInitialChannel()\n }, [initialParticipantFilter, client, isConnected, service, debug])\n\n // Leave / block clears the selected channel and notifies the consumer.\n // The consumer owns what happens next — typically unmounting MessagingShell\n // or re-rendering with new participant data. When no callback is provided,\n // the shell renders an \"ended\" state rather than an indefinite spinner so\n // the surface remains honest about why no conversation is shown.\n const onExitConversationRef = useRef(onExitConversation)\n onExitConversationRef.current = onExitConversation\n const handleExitConversation = useCallback(() => {\n setSelectedChannel(null)\n setDidExit(true)\n onExitConversationRef.current?.()\n }, [])\n\n if (isLoading) {\n return <LoadingState />\n }\n\n if (error) {\n return <ErrorState message={error} onBack={refreshConnection} />\n }\n\n if (!isConnected || !client) {\n return (\n <ErrorState\n message=\"Not connected to messaging service\"\n onBack={refreshConnection}\n />\n )\n }\n\n if (directConversationError) {\n return <ErrorState message={directConversationError} />\n }\n\n if (didExit && !selectedChannel) {\n return <ErrorState message=\"Conversation ended\" />\n }\n\n if (!selectedChannel) {\n return <LoadingState />\n }\n\n return (\n <div className=\"messaging-shell h-full bg-background-primary overflow-hidden\">\n <div className=\"flex h-full min-h-0 flex-col\">\n <ChannelView\n channel={selectedChannel}\n key={selectedChannel.id}\n renderMessageInputActions={renderMessageInputActions}\n renderConversationFooter={renderConversationFooter}\n renderChannelBanner={renderChannelBanner}\n onLeaveConversation={handleExitConversation}\n onBlockParticipant={handleExitConversation}\n CustomChannelEmptyState={CustomChannelEmptyState}\n showDeleteConversation={showDeleteConversation}\n onBlockParticipantClick={onBlockParticipantClick}\n onReportParticipantClick={onReportParticipantClick}\n dmAgentEnabled={dmAgentEnabled}\n onMessageSent={onMessageSent}\n chatbotVotingEnabled={chatbotVotingEnabled}\n viewerLanguage={viewerLanguage}\n customChannelActions={customChannelActions}\n renderChannelActions={renderChannelActions}\n onParticipantNameClick={onParticipantNameClick}\n onBroadcastClick={onBroadcastClick}\n resolveBroadcastName={resolveBroadcastName}\n renderMessage={renderMessage}\n onMessageLinkClick={onMessageLinkClick}\n showChannelInfo={showChannelInfo}\n composerInput={composerInput}\n />\n </div>\n </div>\n )\n}\n","import type { Channel } from 'stream-chat'\n\n/**\n * Restores pending messages into the channel's visible message list so they\n * appear as if they were already sent.\n *\n * Stream's pending-messages feature removes messages from the channel view\n * once client state is lost (e.g. page refresh). This function works around\n * that limitation by reading `channel.state.pending_messages` and\n * re-inserting them via `channel.state.addMessageSorted`.\n */\nexport function restorePendingMessages(channel: Channel) {\n const pendingMessages = channel.state.pending_messages\n if (!pendingMessages?.length) return\n\n for (const pending of pendingMessages) {\n channel.state.addMessageSorted(pending.message)\n }\n}\n","import React from 'react'\nimport type { Channel, LocalMessage } from 'stream-chat'\n\nimport type { ChannelListProps } from '../../types'\n\ntype ChannelListContextValue = {\n selectedChannel?: Channel | null\n onChannelSelect: (channel: Channel) => void\n debug: boolean\n renderMessagePreview?: (\n message: LocalMessage | undefined,\n defaultPreview?: string\n ) => React.ReactNode\n channelPreview?: ChannelListProps['channelPreview']\n viewerLanguage?: string\n}\n\nconst ChannelListContext = React.createContext<ChannelListContextValue>({\n selectedChannel: undefined,\n onChannelSelect: () => {},\n debug: false,\n renderMessagePreview: undefined,\n channelPreview: undefined,\n viewerLanguage: undefined,\n})\n\nexport const ChannelListProvider = ChannelListContext.Provider\n\nexport const useChannelListContext = () => React.useContext(ChannelListContext)\n","/**\n * Get the number of days between two dates (calendar days in the viewer's\n * local timezone, not 24-hour periods). Uses local days so the bucket a\n * message falls into always agrees with the locale-formatted output\n * (time of day, weekday name, date), which is also rendered in local time.\n */\nconst getDaysDifference = (date1: Date, date2: Date): number => {\n const d1 = Date.UTC(date1.getFullYear(), date1.getMonth(), date1.getDate())\n const d2 = Date.UTC(date2.getFullYear(), date2.getMonth(), date2.getDate())\n return Math.floor((d2 - d1) / (1000 * 60 * 60 * 24))\n}\n\n/**\n * Format a date - shows time for today, relative time for older messages\n * (e.g., \"Just now\", \"2:08 PM\" for today, \"Yesterday\" for yesterday,\n * \"Mon\" for 2-6 days ago, \"Jul 14\" for older dates this year,\n * \"Jul 14, 2025\" for previous years)\n */\nexport const formatRelativeTime = (date: Date): string => {\n const now = new Date()\n const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000)\n\n // Less than 1 minute\n if (diffInSeconds < 60) {\n return 'Just now'\n }\n\n // Check if it's today (same calendar day)\n const daysDiff = getDaysDifference(date, now)\n\n // If today, show time in 12-hour format (e.g., \"8:40 PM\")\n if (daysDiff === 0) {\n return date.toLocaleTimeString([], {\n hour: 'numeric',\n minute: '2-digit',\n hour12: true,\n })\n }\n\n // Yesterday\n if (daysDiff === 1) {\n return 'Yesterday'\n }\n\n // 2-6 days ago - show short weekday name (e.g., \"Mon\")\n if (daysDiff < 7) {\n return date.toLocaleDateString(undefined, { weekday: 'short' })\n }\n\n // 7+ days ago, same local year - show month and day (e.g., \"Jul 14\")\n if (date.getFullYear() === now.getFullYear()) {\n return date.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n })\n }\n\n // Previous years - include the year (e.g., \"Jul 14, 2025\")\n return date.toLocaleDateString(undefined, {\n month: 'short',\n day: 'numeric',\n year: 'numeric',\n })\n}\n","import { MESSAGE_CUSTOM_TYPE } from '@linktr.ee/messaging-taxonomy'\nimport classNames from 'classnames'\nimport React from 'react'\nimport {\n ChannelPreviewUIComponentProps,\n useChatContext,\n} from 'stream-chat-react'\n\nimport { useChannelStar } from '../../hooks/useChannelStar'\nimport { formatRelativeTime } from '../../utils/formatRelativeTime'\nimport { getMessageDisplayText } from '../../utils/getMessageDisplayText'\nimport { resolveConversationParticipant } from '../../utils/resolveConversationParticipant'\nimport { resolveParticipantDisplayName } from '../../utils/resolveParticipantDisplayName'\nimport { Avatar } from '../Avatar'\nimport { isChatbotMessage } from '../CustomMessage/MessageTag'\n\nimport { useChannelListContext } from './ChannelListContext'\n\n/**\n * Custom channel preview that handles selection\n */\nconst CustomChannelPreview = React.memo<ChannelPreviewUIComponentProps>(\n (props) => {\n const { channel, unread } = props\n const {\n selectedChannel,\n onChannelSelect,\n debug,\n channelPreview,\n renderMessagePreview,\n viewerLanguage,\n } = useChannelListContext()\n const { client } = useChatContext()\n\n const isSelected = selectedChannel?.id === channel?.id\n const isChannelStarred = useChannelStar(channel)\n\n if (channelPreview) {\n const ChannelPreview = channelPreview\n\n return (\n <ChannelPreview\n {...props}\n active={isSelected}\n selectedChannel={selectedChannel}\n onChannelSelect={onChannelSelect}\n viewerLanguage={viewerLanguage}\n />\n )\n }\n\n const handleClick = () => {\n if (channel) {\n onChannelSelect(channel)\n }\n }\n const handleKeyDown = (event: React.KeyboardEvent<HTMLDivElement>) => {\n const isActivationKey = event.key === 'Enter' || event.key === ' '\n const isRepeatedKeydown = event.repeat\n\n if (!isActivationKey || isRepeatedKeydown) return\n\n event.preventDefault()\n handleClick()\n }\n\n // Get participant info\n const participant = resolveConversationParticipant(\n channel?.state?.members,\n client?.userID,\n channel?.type\n )\n const participantName = resolveParticipantDisplayName(participant?.user)\n const participantImage = participant?.user?.image\n\n // Get last non-system message for preview\n const lastMessage = (() => {\n const messages = channel?.state?.messages\n if (!messages?.length) return undefined\n for (let i = messages.length - 1; i >= 0; i--) {\n if (messages[i].type !== 'system') return messages[i]\n }\n return undefined\n })()\n\n const getLastMessageText = () => {\n const displayText = getMessageDisplayText({\n message: lastMessage,\n viewerLanguage,\n })\n\n const { custom_type, attachment_content_type } =\n lastMessage?.metadata || {}\n\n if (custom_type === MESSAGE_CUSTOM_TYPE.TIP) {\n return displayText ? displayText : 'Sent a tip'\n }\n if (custom_type === MESSAGE_CUSTOM_TYPE.PAID) {\n return displayText ? displayText : 'Sent a message'\n }\n if (custom_type === MESSAGE_CUSTOM_TYPE.ATTACHMENT) {\n if (attachment_content_type === 'text') {\n return '🔒 Sent a locked message'\n }\n if (attachment_content_type === 'media') {\n return '🔒 Sent a locked attachment'\n }\n }\n\n if (displayText) return displayText\n\n const attachment = lastMessage?.attachments?.[0]\n if (attachment) {\n // Link previews - show the actual URL\n if (attachment.og_scrape_url) return attachment.og_scrape_url\n\n // Type-based detection\n if (attachment.type === 'image') return '📷 Sent an image'\n if (attachment.type === 'video') return '🎥 Sent a video'\n if (attachment.type === 'audio') return '🎵 Sent audio'\n if (attachment.type === 'file') return '📎 Sent a file'\n\n // Fallback\n return '📎 Sent an attachment'\n }\n\n return 'No messages yet'\n }\n\n const lastMessageText = getLastMessageText()\n const lastMessageTime = lastMessage?.created_at\n ? formatRelativeTime(new Date(lastMessage.created_at))\n : ''\n const isLastMessageFromChatbot = lastMessage\n ? isChatbotMessage(lastMessage)\n : false\n\n const messagePreview = renderMessagePreview\n ? renderMessagePreview(lastMessage, lastMessageText)\n : `${isLastMessageFromChatbot ? '✨ ' : ''}${lastMessageText}`\n\n // Use the unread prop passed by Stream Chat (reactive and updates automatically)\n const unreadCount = unread ?? 0\n\n if (debug) {\n console.log('📺 [ChannelList] 📋 CHANNEL PREVIEW RENDER', {\n channelId: channel?.id,\n isSelected,\n participantName,\n unreadCount,\n hasTimestamp: !!lastMessageTime,\n })\n }\n\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onClick={handleClick}\n onKeyDown={handleKeyDown}\n className={classNames(\n 'group w-full px-4 py-3 transition-colors text-left max-w-full overflow-hidden focus-ring rounded-[12px] [&+&]:mt-2',\n {\n 'bg-black/[0.04]': isSelected,\n 'hover:bg-black/[0.02]': !isSelected,\n }\n )}\n data-dd-privacy=\"mask\"\n >\n <div className=\"flex items-start gap-4\">\n {/* Avatar */}\n <Avatar\n id={participant?.user?.id || channel.id || 'unknown'}\n name={participantName}\n image={participantImage}\n size={48}\n starred={isChannelStarred}\n />\n\n {/* Content column */}\n <div className=\"flex-1 min-w-0 flex flex-col gap-1\">\n {/* Name and timestamp row */}\n <div className=\"flex items-center justify-between gap-2\">\n <h3\n className={classNames(\n 'text-sm font-medium truncate text-[#191918]'\n )}\n >\n {isChannelStarred && (\n <span className=\"sr-only\">Starred conversation. </span>\n )}\n {participantName}\n </h3>\n {lastMessageTime && (\n <span className=\"text-xs text-[#717070] flex-shrink-0\">\n {lastMessageTime}\n </span>\n )}\n </div>\n\n {/* Message and unread badge row */}\n <div className=\"flex items-center justify-between gap-2 min-w-0\">\n <p className=\"text-sm text-[#717070] flex-1 line-clamp-1\">\n {messagePreview}\n </p>\n {unreadCount > 0 && (\n <span className=\"bg-[#7f22fe] text-white text-[10px] rounded-full h-4 flex items-center justify-center p-1 min-w-4 text-center flex-shrink-0\">\n {unreadCount > 99 ? '99+' : unreadCount}\n </span>\n )}\n </div>\n </div>\n </div>\n </div>\n )\n }\n)\n\nexport default CustomChannelPreview\nCustomChannelPreview.displayName = 'CustomChannelPreview'\n","import classNames from 'classnames'\nimport React from 'react'\nimport type { Channel } from 'stream-chat'\nimport {\n ChannelList as StreamChannelList,\n InfiniteScroll,\n} from 'stream-chat-react'\n\nimport { restorePendingMessages } from '../../hooks/useRestorePendingMessages'\nimport { useMessagingContext } from '../../providers/MessagingProvider'\nimport type { ChannelListProps } from '../../types'\n\nimport { ChannelListProvider } from './ChannelListContext'\nimport CustomChannelPreview from './CustomChannelPreview'\n\nconst DEFAULT_SORT = { last_message_at: -1 } as const\n\n/**\n * Channel list component with customizable header and actions\n */\nexport const ChannelList = React.memo<ChannelListProps>(\n ({\n onChannelSelect,\n selectedChannel,\n filters,\n allowNewMessagesFromUnfilteredChannels = false,\n channelRenderFilterFn,\n sort = DEFAULT_SORT,\n className,\n customEmptyStateIndicator,\n channelListPaginator: Paginator = InfiniteScroll,\n channelPreview,\n renderMessagePreview,\n viewerLanguage,\n lockChannelOrder,\n onMessageNewHandler,\n onMessageNew,\n onChannelVisible,\n onAddedToChannel,\n onChannelUpdated,\n }) => {\n // Track renders\n const renderCountRef = React.useRef(0)\n renderCountRef.current++\n\n // Get debug flag from context\n const { debug = false } = useMessagingContext()\n\n // Wrap channelRenderFilterFn to restore pending messages for all channels\n // as soon as they are loaded, without waiting for the user to click into each one.\n const wrappedChannelRenderFilterFn = React.useCallback(\n (channels: Channel[]) => {\n for (const channel of channels) {\n restorePendingMessages(channel)\n }\n return channelRenderFilterFn\n ? channelRenderFilterFn(channels)\n : channels\n },\n [channelRenderFilterFn]\n )\n\n if (debug) {\n console.log('📺 [ChannelList] 🔄 RENDER START', {\n renderCount: renderCountRef.current,\n selectedChannelId: selectedChannel?.id,\n filters,\n })\n }\n\n const contextValue = React.useMemo(\n () => ({\n selectedChannel,\n onChannelSelect,\n debug,\n channelPreview,\n renderMessagePreview,\n viewerLanguage,\n }),\n [\n selectedChannel,\n onChannelSelect,\n debug,\n channelPreview,\n renderMessagePreview,\n viewerLanguage,\n ]\n )\n\n return (\n <div\n className={classNames(\n 'messaging-channel-list h-full flex flex-col min-w-0 overflow-hidden',\n className\n )}\n >\n {/* Channel List */}\n <div className=\"flex-1 overflow-hidden min-w-0\">\n <ChannelListProvider value={contextValue}>\n <StreamChannelList\n key={`${JSON.stringify(filters)}:${JSON.stringify(sort)}`}\n filters={filters}\n sort={sort}\n options={{ limit: 30 }}\n allowNewMessagesFromUnfilteredChannels={\n allowNewMessagesFromUnfilteredChannels\n }\n channelRenderFilterFn={wrappedChannelRenderFilterFn}\n lockChannelOrder={lockChannelOrder}\n onMessageNewHandler={onMessageNewHandler}\n onMessageNew={onMessageNew}\n onChannelVisible={onChannelVisible}\n onAddedToChannel={onAddedToChannel}\n onChannelUpdated={onChannelUpdated}\n Paginator={Paginator}\n Preview={CustomChannelPreview}\n EmptyStateIndicator={customEmptyStateIndicator}\n />\n </ChannelListProvider>\n </div>\n </div>\n )\n }\n)\nChannelList.displayName = 'ChannelList'\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport CardCta from '../LinkAttachment/components/_shared/CardCta'\nimport type { LinkAttachmentVariant } from '../LinkAttachment/components/_shared/CardShell'\n\nimport type { RichCardActionButton } from './types'\n\nexport interface RichCardBodyProps {\n /** Surface the action buttons paint against (from `useChinPalette().surface`). */\n ctaSurface: LinkAttachmentVariant\n /** Ink for the title + subtitle, from `useChinPalette` — fixed per surface. */\n chinTextColor: string\n title?: string\n subtitle?: string\n subtitleLines?: number\n appIcon?: React.ReactNode\n actions?: RichCardActionButton[]\n}\n\n// Mirror LinkAttachment's `CardBody` type scales so the two cards read\n// identically (14/20 medium title, 12/16 subtitle). Colour is always supplied\n// via `chinTextColor`, so no per-variant text class is needed here.\nconst TITLE_CLASS = 'line-clamp-2 text-[14px] font-medium leading-5 tracking-[0.28px]'\nconst SUBTITLE_CLASS = 'text-[12px] leading-4 tracking-[0.24px]'\n\n/**\n * The action footer. `layout` (chosen by the first action's `variant` in\n * {@link RichCardBody}) picks the presentation: `stacked` (`primary`) →\n * full-width buttons stacked below the copy; `row` (`secondary`) → content-width\n * buttons pinned to the right of the chin row (`shrink-0`, so they never\n * compress the title/subtitle). Each button still paints in its own emphasis.\n * Returns `null` when there are no actions.\n */\nconst RichCardFooter: React.FC<{\n surface: LinkAttachmentVariant\n actions?: RichCardActionButton[]\n layout: 'stacked' | 'row'\n}> = ({ surface, actions, layout }) => {\n if (actions == null || actions.length === 0) return null\n\n const stacked = layout === 'stacked'\n\n return (\n <div\n className={classNames(\n 'flex gap-2',\n // `row` is capped (`max-w-[60%]`) and shrinkable (`min-w-0`) so a long\n // or localized secondary label can never grow the footer past the\n // card's width; the button truncates within it and the copy yields\n // first. `stacked` keeps the full-width buttons-below layout.\n stacked\n ? 'mt-3 flex-col'\n : 'min-w-0 max-w-[60%] flex-row flex-wrap justify-end'\n )}\n >\n {actions.map((action, index) => (\n <CardCta\n key={`${action.label}-${index}`}\n variant={surface}\n cta={action}\n fullWidth={stacked}\n standalone\n />\n ))}\n </div>\n )\n}\n\n/**\n * The rich card's \"chin\": title, subtitle (prose, multi-line by default), and\n * the action footer. Padding matches `CardBody` (16px x, 12px y) so a rich card\n * lines up with the link cards around it.\n */\nconst RichCardBody: React.FC<RichCardBodyProps> = ({\n ctaSurface,\n chinTextColor,\n title,\n subtitle,\n subtitleLines = 3,\n appIcon,\n actions,\n}) => {\n const hasTitle = title != null && title.trim() !== ''\n const hasSubtitle = subtitle != null && subtitle.trim() !== ''\n const hasActions = actions != null && actions.length > 0\n\n if (!hasTitle && !hasSubtitle && !hasActions) return null\n\n const chinTextStyle: React.CSSProperties = { color: chinTextColor }\n const subtitleStyle: React.CSSProperties =\n subtitleLines > 1\n ? {\n ...chinTextStyle,\n display: '-webkit-box',\n WebkitBoxOrient: 'vertical',\n WebkitLineClamp: subtitleLines,\n overflow: 'hidden',\n }\n : chinTextStyle\n\n // A secondary primary-action lays out as a row (copy left, action right);\n // primary stacks the button full-width below the copy.\n const inline =\n hasActions && (actions?.[0]?.variant ?? 'primary') === 'secondary'\n\n const textBlock = (\n <div className=\"flex min-w-0 flex-1 flex-col gap-[2px]\">\n {hasTitle && (\n <div className=\"flex min-w-0 items-center gap-2\">\n {appIcon ? <span className=\"shrink-0\">{appIcon}</span> : null}\n <p className={classNames('min-w-0', TITLE_CLASS)} style={chinTextStyle}>\n {title}\n </p>\n </div>\n )}\n\n {hasSubtitle && (\n <p\n className={classNames(\n SUBTITLE_CLASS,\n subtitleLines <= 1 && 'truncate'\n )}\n style={subtitleStyle}\n >\n {subtitle}\n </p>\n )}\n </div>\n )\n\n return (\n <div className=\"shrink-0 px-4 py-3\">\n {inline ? (\n <div className=\"flex items-center justify-between gap-3\">\n {textBlock}\n <RichCardFooter surface={ctaSurface} actions={actions} layout=\"row\" />\n </div>\n ) : (\n <>\n {textBlock}\n <RichCardFooter surface={ctaSurface} actions={actions} layout=\"stacked\" />\n </>\n )}\n </div>\n )\n}\n\nexport default RichCardBody\n","import type { RichCardImagePresentation } from '@linktr.ee/messaging-taxonomy'\nimport classNames from 'classnames'\nimport React from 'react'\n\n\nimport { optimizeMessagingAttachmentUrl } from '../../utils/cdnImageUrl'\n// Reuse LinkAttachment's card chrome primitives — the rich card is a sibling\n// renderer over the same building blocks, not a from-scratch card.\nimport type { LinkAttachmentVariant } from '../LinkAttachment/components/_shared/CardShell'\nimport CardThumbnail from '../LinkAttachment/components/_shared/CardThumbnail'\n\nexport interface RichCardImagesProps {\n variant: LinkAttachmentVariant\n presentation: RichCardImagePresentation\n images: string[]\n title?: string\n /** Hero-only: the palette-tracked URL (cleared when the hero image errors). */\n heroUrl?: string\n onHeroError?: () => void\n}\n\n/** Placeholder fill behind the fanned images, per surface. */\nconst FAN_BG: Record<LinkAttachmentVariant, string> = {\n dark: 'bg-white/[0.06]',\n light: 'bg-black/[0.04]',\n}\n\n// Deterministic fan geometry (no randomness — Chromatic snapshots must be\n// stable). Rotation + horizontal offset per slot, keyed on how many images are\n// actually shown (survivors after any load failures) — so a fan that drops to\n// two or one re-centres rather than leaving a gap.\nconst FAN_ROTATION: Record<1 | 2 | 3, number[]> = {\n 1: [0],\n 2: [-10, 10],\n 3: [-13, 0, 13],\n}\nconst FAN_OFFSET_PX: Record<1 | 2 | 3, number[]> = {\n 1: [0],\n 2: [-30, 30],\n 3: [-44, 0, 44],\n}\n// Paint z-order per slot so the **centre** card sits on top. Survivors render\n// left→right, so with the naive `zIndex: position` the right card overlaps the\n// middle; here the centre (slot 1 of a 3-fan) gets the highest layer.\nconst FAN_ZORDER: Record<1 | 2 | 3, number[]> = {\n 1: [0],\n 2: [0, 1],\n 3: [1, 3, 2],\n}\n// The centre card of a 3-fan is nudged larger so it reads as the top of the\n// stack rather than one of three equals (mirrors the collection design).\nconst FAN_SCALE: Record<1 | 2 | 3, number[]> = {\n 1: [1],\n 2: [1, 1],\n 3: [1, 1.08, 1],\n}\n\n/**\n * A fanned stack of 2–3 thumbnails for the `fan` presentation (collection\n * cards). An image that fails to load is dropped and the survivors re-fan (the\n * hero path recovers the same way via `useChinPalette`); if all fail the region\n * is left blank. An initial visual interpretation — the exact fan geometry\n * should be reconciled against the Figma collection frames when they land.\n */\nconst RichCardFan: React.FC<{\n variant: LinkAttachmentVariant\n images: string[]\n}> = ({ variant, images }) => {\n const [failed, setFailed] = React.useState<Record<number, boolean>>({})\n\n // Reset the per-tile failure tracking when the image set changes, so a\n // recycled card (same element, new collection) doesn't carry a previous\n // array's failed indexes onto a valid replacement. Adjusting state during\n // render on a key change is the endorsed alternative to a set-state effect\n // (mirrors how `useChinPalette` clears `heroFailed` when `thumbnailUrl` changes).\n const imagesKey = images.join('\\n')\n const [trackedKey, setTrackedKey] = React.useState(imagesKey)\n if (imagesKey !== trackedKey) {\n setTrackedKey(imagesKey)\n setFailed({})\n }\n\n const survivors = images\n .slice(0, 3)\n .map((src, index) => ({ src, index }))\n .filter(({ index }) => !failed[index])\n\n const count = (\n survivors.length >= 3 ? 3 : survivors.length === 2 ? 2 : 1\n ) as 1 | 2 | 3\n const rotations = FAN_ROTATION[count]\n const offsets = FAN_OFFSET_PX[count]\n const zorder = FAN_ZORDER[count]\n const scales = FAN_SCALE[count]\n\n return (\n <div\n className={classNames(\n 'relative flex min-h-0 w-full flex-1 items-center justify-center overflow-hidden',\n FAN_BG[variant]\n )}\n >\n {survivors.map(({ src, index }, position) => (\n <img\n key={`${src}-${index}`}\n src={optimizeMessagingAttachmentUrl(src) ?? src}\n // Decorative: the card's title + subtitle carry the meaning, so the\n // fan thumbnails are illustration only. Empty alt keeps them out of\n // the accessibility tree (the accessible content is the text).\n alt=\"\"\n draggable={false}\n loading=\"lazy\"\n decoding=\"async\"\n onError={() => setFailed((prev) => ({ ...prev, [index]: true }))}\n // A uniform, slightly-landscape tile (`h-74% w-66%`) with `object-cover`\n // so the fan stays tidy whatever the source thumbnails are (OG scrape,\n // upload, video frame) — the tiles are decorative, so an even cover\n // crop reads better than per-image aspect. `rounded-[12px]` explicit,\n // NOT `rounded-xl` — this preset's `xl` radius is 4rem, which rounds a\n // tile into an oval. A soft drop shadow (no border) separates the\n // overlapping tiles.\n className=\"absolute left-1/2 top-1/2 h-[74%] w-[66%] rounded-[12px] object-cover shadow-lg\"\n style={{\n transform: `translate(-50%, -50%) translateX(${offsets[position]}px) rotate(${rotations[position]}deg) scale(${scales[position]})`,\n zIndex: zorder[position],\n }}\n />\n ))}\n </div>\n )\n}\n\n/** Fixed image-region height. The chin flows below at its natural height (the\n * card grows rather than clipping tall content), so this is a fixed height, not\n * the 250px \"hero absorbs the remainder\" lock LinkAttachment uses. */\nconst IMAGE_REGION_CLASS =\n 'flex h-[180px] w-full shrink-0 flex-col overflow-hidden'\n\n/**\n * The rich card's image region. `hero` reuses `CardThumbnail` (single image, the\n * official/link-share card); `fan` renders the fanned stack (collection card).\n * Both fill a fixed-height flex column so `CardThumbnail`'s `flex-1` hero has a\n * height to occupy without the whole card being pinned to a fixed box.\n */\nconst RichCardImages: React.FC<RichCardImagesProps> = ({\n variant,\n presentation,\n images,\n title,\n heroUrl,\n onHeroError,\n}) => (\n <div className={IMAGE_REGION_CLASS}>\n {presentation === 'fan' ? (\n <RichCardFan variant={variant} images={images} />\n ) : (\n // `heroUrl` is the palette-tracked source: it becomes `undefined` when the\n // hero image errors, so `CardThumbnail` falls through to its placeholder.\n // Do NOT `?? images[0]` here — that would re-instate the failed URL.\n <CardThumbnail\n variant={variant}\n thumbnailUrl={heroUrl}\n title={title}\n mimeType=\"image/*\"\n onImageError={onHeroError}\n />\n )}\n </div>\n)\n\nexport default RichCardImages\n","import { resolveRichCardImagePresentation } from '@linktr.ee/messaging-taxonomy'\nimport React from 'react'\n\n\nimport BubbleTail from '../LinkAttachment/components/_shared/BubbleTail'\nimport CardShell from '../LinkAttachment/components/_shared/CardShell'\nimport type { LinkAttachmentVariant } from '../LinkAttachment/components/_shared/CardShell'\nimport { useChinPalette } from '../LinkAttachment/components/_shared/useChinPalette'\n\nimport RichCardBody from './RichCardBody'\nimport RichCardImages from './RichCardImages'\nimport type { RichCardBaseProps } from './types'\n\nexport interface RichCardCardProps extends RichCardBaseProps {\n variant: LinkAttachmentVariant\n}\n\n/**\n * The shared internal rich-card renderer, parameterised by surface. `Sent`\n * (dark) and `Received` (light) are thin wrappers over it. Composes\n * LinkAttachment's card chrome primitives (`CardShell`, `useChinPalette`,\n * `BubbleTail`) with the rich-card-specific image region + multi-action chin.\n */\nconst RichCardCard: React.FC<RichCardCardProps> = ({\n variant,\n title,\n subtitle,\n subtitleLines,\n images = [],\n imagePresentation,\n actions,\n appIcon,\n accentColor,\n groupPosition = 'single',\n}) => {\n const presentation = resolveRichCardImagePresentation({\n imagePresentation,\n images,\n })\n const hasImages = images.length > 0\n const isHero = hasImages && presentation === 'hero'\n\n // Accent derives only from a single on-screen hero image — a `fan` (or\n // image-less) card keeps the plain fill. Passing the hero URL through\n // `useChinPalette` reuses its hero-failure tracking so a broken hero drops\n // the accent with it, exactly as LinkAttachment does.\n const chin = useChinPalette({\n variant,\n accentColor,\n layout: 'featured',\n thumbnailUrl: isHero ? images[0] : undefined,\n })\n\n const tailVisible =\n groupPosition === 'single' || groupPosition === 'end'\n\n return (\n <BubbleTail\n accentHex={chin.accentHex}\n side={variant === 'dark' ? 'sender' : 'receiver'}\n visible={tailVisible}\n >\n {/* No `fixedHeight`: the image region owns a fixed height and the chin\n flows below at its natural height, so a card with a multi-line\n subtitle and several stacked actions grows instead of clipping. */}\n <CardShell\n variant={variant}\n accentHex={chin.accentHex}\n data-testid=\"rich-card\"\n >\n {hasImages && (\n <RichCardImages\n variant={variant}\n presentation={presentation}\n images={images}\n title={title}\n heroUrl={isHero ? chin.thumbnailUrl : undefined}\n onHeroError={chin.onImageError}\n />\n )}\n <RichCardBody\n ctaSurface={chin.surface}\n chinTextColor={chin.chinTextColor}\n title={title}\n subtitle={subtitle}\n subtitleLines={subtitleLines}\n appIcon={appIcon}\n actions={actions}\n />\n </CardShell>\n </BubbleTail>\n )\n}\n\nexport default RichCardCard\n","import React from 'react'\n\nimport RichCardCard from './RichCardCard'\nimport type { RichCardBaseProps } from './types'\n\n/** Props for `RichCard.Sent` / `RichCard.Received`. */\nexport type RichCardSentCardProps = RichCardBaseProps\nexport type RichCardReceivedCardProps = RichCardBaseProps\n\nconst SentCard: React.FC<RichCardSentCardProps> = (props) => (\n <RichCardCard {...props} variant=\"dark\" />\n)\n\nconst ReceivedCard: React.FC<RichCardReceivedCardProps> = (props) => (\n <RichCardCard {...props} variant=\"light\" />\n)\n\n/**\n * The generic rich message card (`linktree_rich_card`) — the shared render\n * primitive for rich message surfaces. Render `RichCard.Sent` for the sender's\n * own copy (dark chrome) and `RichCard.Received` in the recipient's thread\n * (light chrome). The official welcome card and broadcast link/collection cards\n * are all this one card at different knobs:\n *\n * - `hero` + `primary` action → the official welcome card (stacked button)\n * - `hero` + `secondary` action → a single-link broadcast (inline button)\n * - `fan` + `secondary` action → a collection broadcast\n *\n * A standalone renderer, not tied to Stream's `Attachment`: the dispatching\n * consumer narrows an attachment with the taxonomy's `isRichCard`, then maps\n * the taxonomy `RichCard` fields onto these props (resolving each action's\n * `kind` to an `href` / `onClick`).\n */\nconst RichCard = {\n Sent: SentCard,\n Received: ReceivedCard,\n}\n\nexport default RichCard\nexport type {\n RichCardBaseProps,\n RichCardActionButton,\n RichCardImagePresentation,\n} from './types'\n","import React from 'react'\n\nimport type { MessageBubbleVariant } from '../../types'\n\n/**\n * Canonical chat-bubble tail (20×23). Kept identical to the in-channel tail\n * drawn by `CustomMessage` in `styles.css` so standalone previews and real\n * messages share one silhouette — the single source of truth for the shape.\n *\n * The path points to the bottom-*right*; the `received` variant mirrors it\n * with `scaleX(-1)`, matching the in-channel convention.\n */\nconst TAIL_MASK =\n \"url(\\\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='20' height='23' fill='none'%3E%3Cpath fill='black' d='M0 0H15C15 10.1934 15.859 15.2345 19.168 21.4893C19.527 22.168 19.039 22.9917 18.274 22.9178C5.176 21.6506 0.32 6.0737 0 0Z'/%3E%3C/svg%3E\\\") no-repeat center / 100% 100%\"\n\n/**\n * Bubble surface colours, reused from the toolkit's Stream theme vars so the\n * tail is always tinted the same colour as its bubble. Those vars are declared\n * on `.str-chat`, so we supply the canonical values as `var()` fallbacks —\n * that keeps the tail correct when rendered *outside* a channel (e.g. a\n * standalone preview) while still honouring the var (or a consumer override)\n * wherever it's defined.\n */\nconst BG_VAR_BY_VARIANT: Record<MessageBubbleVariant, string> = {\n sent: 'var(--str-chat__own-message-bubble-background-color, #1e2330)',\n received: 'var(--str-chat__message-bubble-background-color, #f1f0ee)',\n}\n\n/**\n * The tail element. Absolutely positioned at the bubble's bottom corner and\n * painted behind the bubble body (`z-index: -1`), so the parent bubble must be\n * `position: relative` with `isolation: isolate` (see {@link BubbleShell}).\n */\nexport const BubbleTail = ({ variant }: { variant: MessageBubbleVariant }) => (\n <span\n aria-hidden=\"true\"\n className=\"pointer-events-none absolute bottom-0 z-[-1] h-[1.4375rem] w-5\"\n style={{\n insetInlineEnd: variant === 'sent' ? 'calc(-1 * 0.25rem)' : undefined,\n insetInlineStart: variant === 'received' ? 'calc(-1 * 0.25rem)' : undefined,\n transform: variant === 'received' ? 'scaleX(-1)' : undefined,\n transformOrigin: 'center',\n backgroundColor: BG_VAR_BY_VARIANT[variant],\n WebkitMask: TAIL_MASK,\n mask: TAIL_MASK,\n }}\n />\n)\n","import classNames from 'classnames'\nimport React from 'react'\n\nimport type { MessageBubbleVariant } from '../../types'\n\nimport { BubbleTail } from './bubbleTail'\n\nexport interface BubbleShellProps {\n variant: MessageBubbleVariant\n text: string\n /** Draw the bubble tail. Defaults to `true`. */\n tail?: boolean\n /** Optional header rendered above the text (e.g. sender name). */\n header?: React.ReactNode\n /**\n * Width is caller-controlled — pass a width class in `className`\n * (`w-fit` to hug the text for chat/preview bubbles, `w-full` to fill the\n * container for card-like list rows). With no width class the bubble is a\n * block element and fills its container.\n */\n className?: string\n}\n\n/**\n * Surface colours and metrics are pulled from the toolkit's Stream theme vars\n * (see `styles.css`) so a standalone bubble is pixel-identical to the\n * in-channel one and re-themes from the same single source of truth.\n */\nconst SURFACE_STYLE_BY_VARIANT: Record<\n MessageBubbleVariant,\n React.CSSProperties\n> = {\n sent: {\n backgroundColor:\n 'var(--str-chat__own-message-bubble-background-color, #1e2330)',\n color: 'var(--str-chat__own-message-bubble-color, #fff)',\n },\n received: {\n backgroundColor:\n 'var(--str-chat__message-bubble-background-color, #f1f0ee)',\n color: 'var(--str-chat__message-bubble-color, #000000)',\n },\n}\n\n/**\n * The rounded bubble box: theme-driven surface, channel-matched typography\n * (`0.875rem / 1.3125rem`, `0.14px` tracking, `0.75rem 1rem` padding), an\n * optional header slot, and an optional tail.\n *\n * `isolation: isolate` forms a stacking context so the `z-index: -1` tail\n * paints behind the bubble's own opaque, same-colour background but in front\n * of the surrounding page — matching the in-channel `::after` tail.\n *\n * `data-dd-privacy=\"mask\"` masks the message text (and header) from Datadog\n * session replay by default, matching the in-channel renderers\n * (`CustomMessage`, `MediaMessage`). Since this bubble is used for standalone\n * previews of real user content outside `ChannelView`, masking must live on\n * the component rather than relying on every consumer to wrap it.\n */\nconst BubbleShell: React.FC<BubbleShellProps> = ({\n variant,\n text,\n tail = true,\n header,\n className,\n}) => (\n <div\n data-dd-privacy=\"mask\"\n className={classNames(\n 'relative px-4 py-3',\n 'text-[0.875rem] leading-[1.3125rem] tracking-[0.14px]',\n className\n )}\n style={{\n ...SURFACE_STYLE_BY_VARIANT[variant],\n borderRadius: 'var(--str-chat__message-bubble-border-radius, 1.5rem)',\n isolation: 'isolate',\n }}\n >\n {header != null && (\n <div className=\"mb-1 font-medium\" data-testid=\"message-bubble-header\">\n {header}\n </div>\n )}\n <p className=\"m-0 whitespace-pre-wrap break-words\">{text}</p>\n {tail && <BubbleTail variant={variant} />}\n </div>\n)\n\nexport default BubbleShell\n","import React from 'react'\n\nimport type { ReceivedBubbleProps } from '../../types'\nimport BubbleShell from '../_shared/BubbleShell'\n\n/**\n * A message bubble in the recipient's treatment — light surface, left-side\n * tail, with an optional header (e.g. sender name). Use to preview how a\n * message looks *to the visitor* — including a preview of your own outgoing\n * message as the recipient will receive it (e.g. a bulk-reply confirmation).\n */\nconst ReceivedCard: React.FC<ReceivedBubbleProps> = ({\n text,\n tail,\n header,\n className,\n}) => (\n <BubbleShell\n variant=\"received\"\n text={text}\n tail={tail}\n header={header}\n className={className}\n />\n)\n\nexport default ReceivedCard\n","import React from 'react'\n\nimport type { SentBubbleProps } from '../../types'\nimport BubbleShell from '../_shared/BubbleShell'\n\n/**\n * A message bubble in the sender's own treatment — dark surface, right-side\n * tail. Use to preview how a message looks *to you in the channel*.\n *\n * Note: the variant is a visual treatment, not an authorship claim. To preview\n * an outgoing message as the *recipient* will see it, use\n * `MessageBubble.Received` instead.\n */\nconst SentCard: React.FC<SentBubbleProps> = ({ text, tail, className }) => (\n <BubbleShell variant=\"sent\" text={text} tail={tail} className={className} />\n)\n\nexport default SentCard\n","import ReceivedCard from './components/Received/Card'\nimport SentCard from './components/Sent/Card'\n\n/**\n * A dumb, context-free chat message bubble for rendering a single message row\n * *outside* a live channel — previews, digests, confirmation dialogs, the\n * Stacks \"responded\" summary, etc. Unlike `ChannelView`, it needs no Stream\n * client or provider.\n *\n * Render `MessageBubble.Sent` for the sender's own treatment (dark, right\n * tail) and `MessageBubble.Received` for the recipient's (light, left tail,\n * optional header). Surface colours, radius, typography, and the tail\n * silhouette are shared with the in-channel bubble drawn by `CustomMessage`,\n * so previews stay pixel-identical and re-theme from one source of truth.\n *\n * The variant is a **visual treatment, not an authorship claim**: to preview\n * an outgoing message as the recipient will see it, render\n * `MessageBubble.Received` with your own text.\n */\nconst MessageBubble = {\n Sent: SentCard,\n Received: ReceivedCard,\n}\n\nexport default MessageBubble\nexport type {\n MessageBubbleBaseProps,\n MessageBubbleVariant,\n SentBubbleProps,\n ReceivedBubbleProps,\n} from './types'\n","import classNames from 'classnames'\nimport React from 'react'\nexport interface FaqListItemProps {\n question: string\n onClick: () => void\n loading?: boolean\n className?: string\n}\n\nexport const FaqListItem: React.FC<FaqListItemProps> = ({\n question,\n onClick,\n loading = false,\n className,\n}) => {\n return (\n <button\n type=\"button\"\n onClick={onClick}\n disabled={loading}\n style={{ backgroundColor: '#E6E5E3' }}\n className={classNames(\n 'w-full text-center p-4 rounded-xl text-charcoal font-medium transition-colors focus-ring',\n {\n 'hover:brightness-95 active:brightness-90': !loading,\n 'opacity-50 cursor-not-allowed': loading,\n },\n className\n )}\n >\n {question}\n </button>\n )\n}\n","import { selectEnabledFaqs } from '@linktr.ee/messaging-taxonomy'\nimport React from 'react'\n\nimport { Avatar } from '../Avatar'\n\nimport { FaqListItem } from './FaqListItem'\n\nexport interface Faq {\n id: string\n question: string\n answer: string\n enabled: boolean\n order?: number | null\n}\n\nexport interface FaqListProps {\n faqs: Faq[]\n onFaqClick: (faqId: string) => void\n loadingFaqId?: string | null\n headerText?: string\n className?: string\n avatarImage?: string\n avatarName?: string\n}\n\nexport const FaqList: React.FC<FaqListProps> = ({\n faqs,\n onFaqClick,\n loadingFaqId,\n headerText,\n className,\n avatarImage,\n avatarName,\n}) => {\n const enabledFaqs = selectEnabledFaqs(faqs)\n\n if (enabledFaqs.length === 0) {\n return null\n }\n\n return (\n <div className={className}>\n <div className=\"flex gap-3 items-end\">\n {/* Avatar at bottom-left, outside grey background */}\n {(avatarImage || avatarName) && (\n <div className=\"flex-none\">\n <Avatar\n id={avatarName || 'account'}\n name={avatarName || 'Account'}\n image={avatarImage}\n size={24}\n shape=\"circle\"\n />\n </div>\n )}\n\n {/* FAQs with grey background */}\n <div\n className=\"flex-1 flex flex-col gap-3 rounded-lg p-4\"\n style={{ backgroundColor: '#F1F0EE' }}\n >\n {headerText && (\n <p className=\"text-md text-charcoal mb-4\">{headerText}</p>\n )}\n {enabledFaqs.map((faq) => (\n <FaqListItem\n key={faq.id}\n question={faq.question}\n onClick={() => onFaqClick(faq.id)}\n loading={loadingFaqId === faq.id}\n />\n ))}\n </div>\n </div>\n </div>\n )\n}\n"],"names":["MessagingContext","createContext","useMessagingContext","useContext","MessagingProvider","children","user","serviceConfig","apiKey","capabilities","debug","injectedClient","debugLog","useCallback","message","args","service","setService","useState","client","setClient","isConnected","setIsConnected","isLoading","setIsLoading","error","setError","connectingRef","useRef","serviceRef","serviceConfigRef","debugRef","prevPropsRef","renderCountRef","clientRef","_a","StreamChat","chatClient","_b","useEffect","currentRender","currentConfig","newService","StreamChatService","connectedUserRef","streamClient","err","errorMessage","refreshConnection","contextValue","React","jsx","Chat","useMessaging","useChannelStar","channel","isChannelStarred","setIsChannelStarred","handleMemberUpdate","event","resolveConversationParticipant","members","currentUserId","channelType","memberList","isOfficialChannel","officialSender","member","OFFICIAL_SENDER_USER_ID","UUID_REGEX","isUuidLike","value","isUsableDisplayValue","userId","trimmed","resolveParticipantDisplayName","FRUITS","hashString","str","hash","i","char","getFruit","id","getAvatarEmoji","getAvatarBgColor","Avatar","image","size","className","starred","shape","dmAgentEnabled","emoji","bgColor","SMALL_EMOJI_RATIO","fontSize","aiBorderWidth","borderStyle","avatarInner","classNames","jsxs","StarIcon","REPORT_URL","useChannelModerationActions","participant","showBlockParticipant","enabled","onLeaveConversation","onBlockParticipant","onDeleteConversationClick","onBlockParticipantClick","onReportParticipantClick","onActionComplete","logLabel","participantId","willLookup","isParticipantBlocked","setIsParticipantBlocked","resolvedFor","setResolvedFor","isLeaving","setIsLeaving","isUpdatingBlockStatus","setIsUpdatingBlockStatus","isCheckingBlockedStatus","cancelled","blockedUsers","actingUserId","_c","ActionButton","variant","rest","ChannelActionsMenu","showDeleteConversation","showReportParticipant","customChannelActions","triggerClassName","isOpen","setIsOpen","containerRef","menuId","useId","close","handleLeaveConversation","handleBlockUser","handleUnblockUser","handleReportUser","handlePointerDown","handleKeyDown","open","DotsThreeIcon","SpinnerGapIcon","SignOutIcon","ProhibitInsetIcon","FlagIcon","ICON_BTN_CLASS","CUSTOMER_PAID_TAG","isCustomerPaidTag","ChannelHeaderRedesign","onBack","showBackButton","showStarButton","renderChannelActions","showChannelInfo","onParticipantNameClick","useChannelStateContext","useChatContext","participantName","participantImage","isStarred","isPaidCustomer","showStarBadge","hasNameAdornment","handleStarClick","identityContent","Fragment","_d","CurrencyDollarIcon","CaretRightIcon","ArrowLeftIcon","CustomDateSeparator","props","DateSeparator","VOTE_UP","VOTE_DOWN","getVoteFromReactions","ownReactions","r","useMessageVote","selected","useMemo","voteUp","voteDown","normalizeLanguageCode","language","getMessageDisplayText","viewerLanguage","fallbackText","normalizedLanguage","LINKTREE_CDN_HOSTS","ATTACHMENT_IMAGE_SIZE_PRESET","hostnameFromUrl","url","isLinktreeCdnHost","hostname","host","optimizeMessagingAttachmentUrl","parsedUrl","DOCUMENT_ICON_PATTERNS","getSourceType","mimeType","getDocumentIconType","match","pattern","MEDIA_TYPE_ICON","VideoCameraIcon","SpeakerHighIcon","ImageIcon","FileIcon","DOCUMENT_ICON_COMPONENT","FilePdfIcon","FileDocIcon","FileXlsIcon","FileCsvIcon","FilePptIcon","FileZipIcon","FileTextIcon","FileMdIcon","getTypeIcon","sourceType","renderTypeIcon","getClientXFromEvent","MediaPlayer","source","poster","autoPlay","playingProp","loop","controls","showProgress","muted","onContainerClick","playerRef","trackRef","rafRef","prevPlayingPropRef","playing","setPlaying","played","setPlayed","seeking","setSeeking","scrubberHovered","setScrubberHovered","manualPlayRequired","setManualPlayRequired","buffering","setBuffering","initialLoad","setInitialLoad","videoAspect","setVideoAspect","startPlaybackFromGesture","getFraction","e","track","rect","seekTo","fraction","el","handleTrackPointerDown","tick","onMove","onUp","aspectStyle","aspectClass","scrubberPercent","p","CircleNotchIcon","PlayIcon","PauseIcon","placeholderIconClass","posterShellClass","AttachmentThumbnail","sourceUrl","thumbnailUrl","title","mediaPlayerProps","containedImage","sourceReady","setSourceReady","optimizedSourceUrl","optimizedThumbnailUrl","AttachmentCard","thumbnail","placeholderTitle","detail","statusBadge","action","topLeft","topRight","rootRef","dataTestId","isDark","displayTitle","titleDimmed","formatBytes","bytes","linkCardShellClass","isMyMessage","linkThumbnailBg","linkPrimaryText","linkSecondaryText","linkTertiaryText","linkIconColor","LinkCard","attachment","text","image_url","og_scrape_url","title_link","rawUrl","body","LinkIcon","isLinkAttachment","a","resolveLinkAttachment","triggerDownload","filename","name","res","blob","objectUrl","DownloadAction","busy","setBusy","fallback","DownloadSimpleIcon","resolveMediaFromMessage","videoAttachment","imageAttachment","audioAttachment","fileAttachment","activeAttachment","resolvedUrl","resolvedType","fileSize","Creator","Visitor","MediaMessageRoot","linkAttachment","resolved","MediaMessageCreatorEntry","MediaMessageVisitorEntry","MediaMessage","BUBBLE_BG_BY_VARIANT","BUBBLE_TEXT_BY_VARIANT","BUBBLE_BORDER_BY_VARIANT","sideForVariant","CORNER_CLASSES_BY_SIDE_AND_POSITION","Bubble","bordered","groupPosition","widthClassName","paddingClassName","radiusClassName","hasText","cornerClasses","DismissButton","onClick","ariaLabel","XIcon","bubbleVariantForState","state","bubbleGroupPositionFromStream","firstOfGroup","endOfGroup","groupedByUser","resolveItems","src","items","NativeAudioPlayer","item","preload","trailingAction","AudioAttachmentRow","onDismiss","showDismiss","resolvedItems","resolvedPreload","index","AudioComposer","AudioSent","AudioReceived","AudioAttachment","formatFileSize","EXTENSION_LABEL_BY_DOCUMENT_TYPE","getFileExtensionLabel","lastDot","ext","docType","docLabel","subtype","buildCompactMetaLabel","filenameFromUrl","last","META_CLASS_BY_VARIANT","ICON_BG_BY_VARIANT","ICON_COLOR_BY_VARIANT","CompactDocumentRow","onActivate","activateLabel","resolvedTitle","metaLabel","iconTile","textBlock","FileRowButton","resolvedFilename","FileAttachmentRow","handleActivate","downloadIcon","FileComposer","FileSent","FileReceived","FileAttachment","CarouselNav","onPrev","onNext","prevLabel","nextLabel","CaretLeftIcon","label","iconOnly","tone","onTriggered","handleClick","showIconOnly","iconProps","clamp","min","max","useCarousel","length","initialIndex","safeInitial","setIndex","prev","next","onKey","active","ViewerShell","onClose","counter","actions","dialogRef","closeButtonRef","dialog","previouslyFocused","ImageViewer","TILE_SHELL","OUTER_CORNER_CLASSES","SINGLE_WIDTH_CAP_PROPERTY","SINGLE_WIDTH_CAPS","SINGLE_HEIGHT_CAP_PX","MediaStackGrid","tiles","onTileActivate","maxVisible","singleAspectRatio","total","visible","overflow","renderTile","tile","extra","sharedClass","singleTileStyle","isLastWithOverflow","useViewer","viewerOpen","setViewerOpen","viewerIndex","setViewerIndex","closeViewer","tileFromItem","totalCount","fallbackLoading","imgRef","onLoad","alt","buildViewerItems","ImageComposerInner","loading","RESERVED_SINGLE_ASPECT_RATIO","useSingleImageRatio","measured","setMeasured","measure","img","ratio","handleLoad","supplied","measuredRatio","ImageBubbleRow","isSingle","single","ImageComposer","ImageSent","ImageReceived","ImageAttachment","PdfViewer","iframeSrc","withPdfViewerParams","hashIndex","base","existing","params","PdfRowButton","PdfAttachmentRow","viewerItems","itemFilename","PdfComposer","PdfSent","PdfReceived","PdfAttachment","VideoViewer","PlayBadge","PosterTile","it","VideoComposerInner","VideoBubbleRow","VideoComposer","VideoSent","VideoReceived","VideoAttachment","MessageAttachment","BroadcastNameContext","broadcastIdFromMessage","broadcastId","buildBroadcastNamesById","messages","resolveBroadcastName","seen","names","areBroadcastNamesEqual","previous","previousIds","nextIds","useBroadcastNamesById","publishedNames","setPublishedNames","defaultLockedAttachmentContextValue","customMessageDefaults","CustomMessageContext","CustomMessageProvider","useCustomMessage","key","TextCardLazy","MediaCardLazy","LockedAttachmentFallback","LockedAttachment","Suspense","SparkleIcon","isTipMessage","MESSAGE_CUSTOM_TYPE","isPaidMessage","isChatbotMessage","isAttachmentMessage","isTextAttachmentMessage","isMediaAttachmentMessage","isTipOrPaidMessage","isTipOnlyMessage","MessageTag","standalone","hasAttachment","isTipOrPaid","isChatbot","amountText","amountLabel","GiftIcon","isSenderAttachmentVariant","chatbotLabel","chatbotClassName","icon","MessageVoteButtons","onVoteUp","onVoteDown","ThumbsUpIcon","ThumbsDownIcon","useIsLatestRealMessage","useMessageContext","lastMessageId","lastMessage","DeliveredStatus","ChecksIcon","SendingStatus","BroadcastStatus","broadcastName","resolvedBroadcastName","onBroadcastClick","campaignName","content","MegaphoneIcon","FailedStatus","WarningCircleIcon","SentMessageDeliveryStatus","threadList","isOwnMessage","isLatestRealMessage","broadcastStatus","messageStatus","MessageStatus","SCHEME_PATTERN","SAFE_SCHEMES","normalizeExternalHref","schemeMatch","scheme","BUTTON_CLASS_BY_VARIANT","CardCta","cta","fullWidth","emphasis","labelNode","normalizedHref","TITLE_CLASS_BY_VARIANT","TITLE_DIMMED_CLASS","SECONDARY_CLASS_BY_VARIANT","SKELETON_BAR_CLASS_BY_VARIANT","CardBody","ctaSurface","chinTextColor","description","descriptionLines","appIcon","hasTitle","hasDescription","trimmedUrl","hasUrl","hasCta","titleClass","secondaryClass","descriptionClass","barClass","chinTextStyle","descriptionStyle","LOADING_LABEL","SHELL_CLASS","FIXED_HEIGHT_CLASS","CardShell","href","bgClassName","accentHex","fixedHeight","isInteractive","busyProps","accentStyle","corner","PLACEHOLDER_BG","PLACEHOLDER_ICON","isPlayableAudio","isPlayableMedia","AUDIO_BG_CLASS","CardThumbnail","onImageError","isPlayableVideo","HEX_PATTERN","toCssHex","hex","INK_ON_DARK","INK_ON_LIGHT","INK_BY_VARIANT","useChinPalette","accentColor","status","layout","heroFailed","setHeroFailed","isAudio","DISMISS_CLASS","DISMISS_TOUCH_TARGET_CLASS","ComposerCard","onEditClick","dismissLabel","isClassic","chin","dismissButton","editButton","PencilSimpleIcon","BubbleTail","side","ReceivedCard","isPlayingMedia","normalizedUrl","shellHref","shellOnClick","audioBg","wrappedCta","SentCard","LinkAttachment","trimToUndefined","normalizeLinkUrl","linkCaptionDuplicatesAttachmentUrl","caption","attachmentUrl","normalizedCaption","remainder","linkCardPropsFromStreamAttachment","getAttachmentMediaKind","ogScrapeUrl","buildOrderedAttachmentSegments","attachments","segments","pushOrMergeMedia","kind","pushOrMergeLink","countMediaSegmentBubbles","segment","bubblePositionInRun","streamPosition","imageSrcFromAttachment","messageAttachmentNamespaceForKind","buildMediaClusterRenderProps","MediaClusterBubble","TypeNamespace","StreamAttachmentMessage","renderText","totalMediaBubbles","rawCaption","linkSegment","primaryLinkUrl","mediaBubbleIndex","rows","linkTailPosition","pushLinkRow","isFinalRow","Card","pushMediaCluster","position","segmentIndex","isFinalSegment","attachmentIndex","MessageErrorIcon","MessageText","CustomMessageWithContext","additionalMessageInputProps","chatbotVotingEnabled","editing","handleAction","handleOpenThread","handleRetry","highlighted","isMessageAIGenerated","isUnlocking","onUnlockClick","onFetchSource","onDownloadClick","isBounceDialogOpen","setIsBounceDialogOpen","reminder","useMessageReminder","voteState","Attachment","DefaultAttachment","EditMessageModal","DefaultEditMessageModal","MessageActions","MessageBlocked","DefaultMessageBlocked","MessageBouncePrompt","DefaultMessageBouncePrompt","MessageDeleted","DefaultMessageDeleted","MessageIsThreadReplyInChannelButtonIndicator","DefaultMessageIsThreadReplyInChannelButtonIndicator","MessageRepliesCountButton","DefaultMessageRepliesCountButton","ReminderNotification","DefaultReminderNotification","StreamedMessageText","DefaultStreamedMessageText","PinIndicator","useComponentContext","messageHasAttachments","hasReactions","messageHasReactions","isAIGenerated","finalAttachments","raw","filtered","attachmentSegments","displayMessage","displayText","hasAttachmentSegments","allAttachmentsRepresented","streamAttachmentGroupPosition","messageAttachmentGroupPositionFromStream","isDateSeparatorMessage","isMessageBlocked","showReplyCountButton","showIsReplyInChannel","allowRetry","isBounced","isMessageBounced","isMine","rootClassName","poll","isTipOnly","isAttachment","hasRenderableAttachments","useAttachmentFooterChatbotTag","canRenderAttachmentsInToolkit","sharedLockedTextProps","sharedLockedMediaProps","_e","_f","_g","_h","_i","_j","MessageBounceModal","Poll","MemoizedCustomMessage","areMessageUIPropsEqual","CustomMessage","messageContext","broadcastNamesById","DeleteAction","handleDelete","DefaultDropdownActionButton","TrashSimpleIcon","ReportAction","handleFlag","CustomMessageActions","linkPreviewsManagerStateSelector","preview","LinkPreviewsManager","CustomLinkPreviewList","linkPreviewsManager","useMessageComposer","stateLinkPreviews","useStateStore","handleDismiss","linkPreview","ComposerLockedContext","NEAR_BOTTOM_THRESHOLD_PX","useComposerLocked","DefaultSendButton","sendMessage","disabled","ArrowUpIcon","CustomMessageInputInner","handleSubmit","useMessageInputContext","hasSendableData","useMessageComposerHasSendableData","isSendDisabled","SendButton","AttachmentPreviewList","DefaultAttachmentPreviewList","cardRef","card","observer","passToBrowser","QuotedMessagePreview","TextareaComposer","CustomMessageInput","renderActions","renderHeader","renderFooter","disabledReason","composerInput","isFrozen","isFloating","composerRef","composer","previousComposerHeight","updateComposerHeight","nextComposerHeight","grew","scroller","wasAtBottom","resizeObserver","MessageInput","DM_AGENT_SYSTEM_MESSAGE_FALLBACK_TEXT","SHOW_SYSTEM_MESSAGE_TIMESTAMP_TYPES","AGE_SAFETY_SYSTEM_MESSAGE_FALLBACK_TEXT","AGE_SAFETY_SYSTEM_MESSAGE_EMPHASIS","AGE_SAFETY_SYSTEM_MESSAGE_URL","isShowTimestampSystemType","shouldShowSystemMessageTimestamp","getCustomSystemMessageVariant","metadataCustomType","isDmAgentSystemType","isAgeSafetySystemType","fallbackType","renderAgeSafetyMessageText","messageText","emphasisIndex","emphasisEndIndex","CustomSystemMessage","isDateHidden","customSystemMessageVariant","MessageTimestamp","ProhibitIcon","DmAgentEnabledContext","Circle","cx","AI_ACTIVE_STATES","AIStates","CustomTypingIndicator","channelConfig","thread","typing","useTypingContext","aiState","useAIState","agentUser","findOtherChannelUser","TypingBubble","typingInChannel","parent_id","typingInThread","typingUsers","typingUser","memberUser","avatarId","avatarName","avatarImage","testId","selfId","ChannelEmptyState","Loading","LoadingState","ChannelViewInner","renderMessageInputActions","renderMessageInputFooter","renderConversationFooter","composerDisabled","composerDisabledReason","renderChannelBanner","renderMessage","onBroadcastClickRef","stableOnBroadcastClick","broadcast","messageBroadcastClick","hasVisibleMessages","channelBanner","channelHeader","MessageOverride","messageNode","WithComponents","Window","MessageList","ChannelView","CustomChannelEmptyState","messageMetadata","onMessageSent","onMessageLinkClick","sendButton","attachmentPreviewList","doSendMessageRequest","_channel","options","agentPaused","shouldSuppressNotifications","finalMessage","finalOptions","response","container","handler","target","anchor","messageWrapper","messageId","m","Channel","ErrorState","MessagingShell","onChannelSelect","onExitConversation","initialParticipantFilter","initialParticipantData","selectedChannel","setSelectedChannel","directConversationError","setDirectConversationError","didExit","setDidExit","initialParticipantDataRef","onChannelSelectRef","directConversationLoadRef","selectedChannelRef","loadKey","releaseLoadGuard","channels","participantData","createErr","onExitConversationRef","handleExitConversation","restorePendingMessages","pendingMessages","pending","ChannelListContext","ChannelListProvider","useChannelListContext","getDaysDifference","date1","date2","d1","d2","formatRelativeTime","date","now","daysDiff","CustomChannelPreview","unread","channelPreview","renderMessagePreview","isSelected","isActivationKey","isRepeatedKeydown","lastMessageText","custom_type","attachment_content_type","lastMessageTime","isLastMessageFromChatbot","messagePreview","unreadCount","DEFAULT_SORT","ChannelList","filters","allowNewMessagesFromUnfilteredChannels","channelRenderFilterFn","sort","customEmptyStateIndicator","Paginator","InfiniteScroll","lockChannelOrder","onMessageNewHandler","onMessageNew","onChannelVisible","onAddedToChannel","onChannelUpdated","wrappedChannelRenderFilterFn","StreamChannelList","TITLE_CLASS","SUBTITLE_CLASS","RichCardFooter","surface","stacked","RichCardBody","subtitle","subtitleLines","hasSubtitle","hasActions","subtitleStyle","inline","FAN_BG","FAN_ROTATION","FAN_OFFSET_PX","FAN_ZORDER","FAN_SCALE","RichCardFan","images","failed","setFailed","imagesKey","trackedKey","setTrackedKey","survivors","count","rotations","offsets","zorder","scales","IMAGE_REGION_CLASS","RichCardImages","presentation","heroUrl","onHeroError","RichCardCard","imagePresentation","resolveRichCardImagePresentation","hasImages","isHero","tailVisible","RichCard","TAIL_MASK","BG_VAR_BY_VARIANT","SURFACE_STYLE_BY_VARIANT","BubbleShell","tail","header","MessageBubble","FaqListItem","question","FaqList","faqs","onFaqClick","loadingFaqId","headerText","enabledFaqs","selectEnabledFaqs","faq"],"mappings":"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"}
|