From 688ee732cb223a228f3107d2099163d6c5e1578d Mon Sep 17 00:00:00 2001 From: Dillion Verma Date: Tue, 2 Jun 2026 09:53:50 -0400 Subject: [PATCH] fix: handle missing tweet entity arrays --- apps/www/public/llms-full.txt | 21 ++++++++++++++++++++- apps/www/public/r/tweet-card.json | 2 +- apps/www/registry/magicui/tweet-card.tsx | 21 ++++++++++++++++++++- 3 files changed, 41 insertions(+), 3 deletions(-) diff --git a/apps/www/public/llms-full.txt b/apps/www/public/llms-full.txt index df1ee56f4..ec3bbf83f 100644 --- a/apps/www/public/llms-full.txt +++ b/apps/www/public/llms-full.txt @@ -19360,6 +19360,19 @@ export const TweetMedia = ({ tweet }: { tweet: EnrichedTweet }) => { ) } +const withSafeEntities = ( + tweet: T +): T & { entities: Tweet["entities"] } => ({ + ...tweet, + entities: { + ...tweet.entities, + hashtags: tweet.entities?.hashtags ?? [], + urls: tweet.entities?.urls ?? [], + symbols: tweet.entities?.symbols ?? [], + user_mentions: tweet.entities?.user_mentions ?? [], + }, +}) + export const MagicTweet = ({ tweet, className, @@ -19368,7 +19381,13 @@ export const MagicTweet = ({ tweet: Tweet className?: string }) => { - const enrichedTweet = enrichTweet(tweet) + const safeTweet: Tweet = { + ...withSafeEntities(tweet), + quoted_tweet: tweet.quoted_tweet + ? withSafeEntities(tweet.quoted_tweet) + : undefined, + } + const enrichedTweet = enrichTweet(safeTweet) return (
(\n \n \n \n \n \n \n)\n\nconst Verified = ({ className, ...props }: TwitterIconProps) => (\n \n \n \n \n \n)\n\nexport const truncate = (str: string | null, length: number) => {\n if (!str || str.length <= length) return str\n return `${str.slice(0, length - 3)}...`\n}\n\nconst Skeleton = ({\n className,\n ...props\n}: React.HTMLAttributes) => {\n return (\n
\n )\n}\n\nexport const TweetSkeleton = ({\n className,\n ...props\n}: {\n className?: string\n [key: string]: unknown\n}) => (\n \n
\n \n \n
\n \n
\n)\n\nexport const TweetNotFound = ({\n className,\n ...props\n}: {\n className?: string\n [key: string]: unknown\n}) => (\n \n

Tweet not found

\n
\n)\n\nexport const TweetHeader = ({ tweet }: { tweet: EnrichedTweet }) => (\n
\n
\n \n \n \n
\n \n {truncate(tweet.user.name, 20)}\n {tweet.user.verified ||\n (tweet.user.is_blue_verified && (\n \n ))}\n \n
\n \n @{truncate(tweet.user.screen_name, 16)}\n \n
\n
\n
\n \n Link to tweet\n \n \n
\n)\n\nexport const TweetBody = ({ tweet }: { tweet: EnrichedTweet }) => (\n
\n {tweet.entities.map((entity, idx) => {\n switch (entity.type) {\n case \"url\":\n case \"symbol\":\n case \"hashtag\":\n case \"mention\":\n return (\n \n {entity.text}\n \n )\n case \"text\":\n return (\n \n )\n default:\n return null\n }\n })}\n
\n)\n\nexport const TweetMedia = ({ tweet }: { tweet: EnrichedTweet }) => {\n if (!tweet.video && !tweet.photos) return null\n return (\n
\n {tweet.video && (\n \n \n Your browser does not support the video tag.\n \n )}\n {tweet.photos && (\n
\n
\n {tweet.photos.map((photo) => (\n \n ))}\n
\n
\n )}\n {!tweet.video &&\n !tweet.photos &&\n // @ts-expect-error package doesn't have type definitions\n tweet?.card?.binding_values?.thumbnail_image_large?.image_value.url && (\n \n )}\n
\n )\n}\n\nexport const MagicTweet = ({\n tweet,\n className,\n ...props\n}: {\n tweet: Tweet\n className?: string\n}) => {\n const enrichedTweet = enrichTweet(tweet)\n return (\n \n \n \n \n
\n )\n}\n\n/**\n * TweetCard (Server Side Only)\n */\nexport const TweetCard = async ({\n id,\n components,\n fallback = ,\n onError,\n ...props\n}: TweetProps & {\n className?: string\n}) => {\n const tweet = id\n ? await getTweet(id).catch((err) => {\n if (onError) {\n onError(err)\n } else {\n console.error(err)\n }\n })\n : undefined\n\n if (!tweet) {\n const NotFound = components?.TweetNotFound ?? TweetNotFound\n return \n }\n\n return (\n \n \n \n )\n}\n", + "content": "import { Suspense } from \"react\"\nimport { enrichTweet, type EnrichedTweet, type TweetProps } from \"react-tweet\"\nimport { getTweet, type Tweet } from \"react-tweet/api\"\n\nimport { cn } from \"@/lib/utils\"\n\ninterface TwitterIconProps {\n className?: string\n [key: string]: unknown\n}\nconst Twitter = ({ className, ...props }: TwitterIconProps) => (\n \n \n \n \n \n \n)\n\nconst Verified = ({ className, ...props }: TwitterIconProps) => (\n \n \n \n \n \n)\n\nexport const truncate = (str: string | null, length: number) => {\n if (!str || str.length <= length) return str\n return `${str.slice(0, length - 3)}...`\n}\n\nconst Skeleton = ({\n className,\n ...props\n}: React.HTMLAttributes) => {\n return (\n
\n )\n}\n\nexport const TweetSkeleton = ({\n className,\n ...props\n}: {\n className?: string\n [key: string]: unknown\n}) => (\n \n
\n \n \n
\n \n
\n)\n\nexport const TweetNotFound = ({\n className,\n ...props\n}: {\n className?: string\n [key: string]: unknown\n}) => (\n \n

Tweet not found

\n
\n)\n\nexport const TweetHeader = ({ tweet }: { tweet: EnrichedTweet }) => (\n
\n
\n \n \n \n
\n \n {truncate(tweet.user.name, 20)}\n {tweet.user.verified ||\n (tweet.user.is_blue_verified && (\n \n ))}\n \n
\n \n @{truncate(tweet.user.screen_name, 16)}\n \n
\n
\n
\n \n Link to tweet\n \n \n
\n)\n\nexport const TweetBody = ({ tweet }: { tweet: EnrichedTweet }) => (\n
\n {tweet.entities.map((entity, idx) => {\n switch (entity.type) {\n case \"url\":\n case \"symbol\":\n case \"hashtag\":\n case \"mention\":\n return (\n \n {entity.text}\n \n )\n case \"text\":\n return (\n \n )\n default:\n return null\n }\n })}\n
\n)\n\nexport const TweetMedia = ({ tweet }: { tweet: EnrichedTweet }) => {\n if (!tweet.video && !tweet.photos) return null\n return (\n
\n {tweet.video && (\n \n \n Your browser does not support the video tag.\n \n )}\n {tweet.photos && (\n
\n
\n {tweet.photos.map((photo) => (\n \n ))}\n
\n
\n )}\n {!tweet.video &&\n !tweet.photos &&\n // @ts-expect-error package doesn't have type definitions\n tweet?.card?.binding_values?.thumbnail_image_large?.image_value.url && (\n \n )}\n
\n )\n}\n\nconst withSafeEntities = (\n tweet: T\n): T & { entities: Tweet[\"entities\"] } => ({\n ...tweet,\n entities: {\n ...tweet.entities,\n hashtags: tweet.entities?.hashtags ?? [],\n urls: tweet.entities?.urls ?? [],\n symbols: tweet.entities?.symbols ?? [],\n user_mentions: tweet.entities?.user_mentions ?? [],\n },\n})\n\nexport const MagicTweet = ({\n tweet,\n className,\n ...props\n}: {\n tweet: Tweet\n className?: string\n}) => {\n const safeTweet: Tweet = {\n ...withSafeEntities(tweet),\n quoted_tweet: tweet.quoted_tweet\n ? withSafeEntities(tweet.quoted_tweet)\n : undefined,\n }\n const enrichedTweet = enrichTweet(safeTweet)\n return (\n \n \n \n \n
\n )\n}\n\n/**\n * TweetCard (Server Side Only)\n */\nexport const TweetCard = async ({\n id,\n components,\n fallback = ,\n onError,\n ...props\n}: TweetProps & {\n className?: string\n}) => {\n const tweet = id\n ? await getTweet(id).catch((err) => {\n if (onError) {\n onError(err)\n } else {\n console.error(err)\n }\n })\n : undefined\n\n if (!tweet) {\n const NotFound = components?.TweetNotFound ?? TweetNotFound\n return \n }\n\n return (\n \n \n \n )\n}\n", "type": "registry:ui" } ] diff --git a/apps/www/registry/magicui/tweet-card.tsx b/apps/www/registry/magicui/tweet-card.tsx index 899c5855a..adb3ed18e 100644 --- a/apps/www/registry/magicui/tweet-card.tsx +++ b/apps/www/registry/magicui/tweet-card.tsx @@ -229,6 +229,19 @@ export const TweetMedia = ({ tweet }: { tweet: EnrichedTweet }) => { ) } +const withSafeEntities = ( + tweet: T +): T & { entities: Tweet["entities"] } => ({ + ...tweet, + entities: { + ...tweet.entities, + hashtags: tweet.entities?.hashtags ?? [], + urls: tweet.entities?.urls ?? [], + symbols: tweet.entities?.symbols ?? [], + user_mentions: tweet.entities?.user_mentions ?? [], + }, +}) + export const MagicTweet = ({ tweet, className, @@ -237,7 +250,13 @@ export const MagicTweet = ({ tweet: Tweet className?: string }) => { - const enrichedTweet = enrichTweet(tweet) + const safeTweet: Tweet = { + ...withSafeEntities(tweet), + quoted_tweet: tweet.quoted_tweet + ? withSafeEntities(tweet.quoted_tweet) + : undefined, + } + const enrichedTweet = enrichTweet(safeTweet) return (