From e471e75514b950e75699b9b2fb990e80c7c2d4a1 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Wed, 10 Sep 2025 14:39:23 -0600 Subject: [PATCH] update weird streaming content bug --- .../AppMarkdownStreaming.stories.tsx | 7 +++++++ .../StreamingMessageCode.tsx | 21 +++++++++++++++---- .../AnimatedMarkdown/animation-helpers.tsx | 10 ++++++++- .../useMarkdownComponents.tsx | 2 +- apps/web/src/controllers/AppNoPageAccess.tsx | 3 +-- apps/web/src/lib/isBrowser.ts | 6 +----- 6 files changed, 36 insertions(+), 13 deletions(-) diff --git a/apps/web/src/components/ui/streaming/AppMarkdownStreaming/AppMarkdownStreaming.stories.tsx b/apps/web/src/components/ui/streaming/AppMarkdownStreaming/AppMarkdownStreaming.stories.tsx index 4ae647f11..807322810 100644 --- a/apps/web/src/components/ui/streaming/AppMarkdownStreaming/AppMarkdownStreaming.stories.tsx +++ b/apps/web/src/components/ui/streaming/AppMarkdownStreaming/AppMarkdownStreaming.stories.tsx @@ -760,3 +760,10 @@ export const InlineCodeStreaming: Story = { ); }, }; + +export const ProblematicCombination: Story = { + args: { + content: + "Let me start by thinking through the TODO items to understand the user's clarification. The user is clarifying that they want users who watched \"at least 75% of each episode of all 3 seasons of Tuttle Twins in the last 48 hours.\"\n\n1. **Confirm understanding that the user wants users who watched at least 75% of EACH individual episode (not 75% of total episodes)**\n The user's clarification confirms exactly what I implemented in my previous analysis. They want users who watched at least 75% completion of each individual episode, not users who watched 75% of the total number of episodes. This means a user must have watched 75%+ of Episode 1 AND 75%+ of Episode 2 AND 75%+ of Episode 3, etc. for all 34 episodes.\n\n2. **Verify that the current analysis already meets this clarified requirement**\n Looking at my previous SQL query, I can see that I already implemented this correctly:\n - I calculated `completion_rate >= 0.75` for each individual episode view\n - I then counted `COUNT(DISTINCT CONCAT(season_name, '|', episode_number))` for episodes where completion_rate >= 0.75\n - I only included users where `episodes_completed_75_percent = total_episode_count` (meaning they completed ALL episodes at 75%+)\n \n This is exactly what the user is asking for - users who watched at least 75% of each individual episode.\n\n3. **Confirm that all 34 episodes across 3 seasons are included in the analysis**\n From my previous analysis, I confirmed there are 34 total episodes:\n - Season 1: 12 episodes\n - Season 2: 12 episodes \n - Season 3: 10 episodes\n - Total: 34 episodes\n \n My query filtered for `season_name IN ('Season 1', 'Season 2', 'Season 3')` which captures all 3 seasons.\n\n4. **Validate that the completion threshold is applied per episode, not aggregated**\n Yes, my query applies the 75% threshold per episode:\n ```sql\n CASE \n WHEN max_playhead_position_sec IS NOT NULL AND duration_sec > 0 \n THEN max_playhead_position_sec::FLOAT / duration_sec \n ELSE 0 \n END as completion_rate\n ```\n Then filters `WHERE completion_rate >= 0.75` before counting episodes.\n\n5. **Determine if any adjustments to the existing metric are needed**\n Based on my analysis, the existing metric already perfectly matches the user's clarified requirement. No adjustments are needed. The user's clarification confirms that my interpretation and implementation were correct.\n\nThe user's clarification actually confirms that my previous analysis was exactly what they wanted. I don't need to make any changes to the metric or query - it already identifies users who watched at least 75% of each individual episode across all 3 seasons in the last 48 hours.", + }, +}; diff --git a/apps/web/src/components/ui/streaming/StreamingMessageCode/StreamingMessageCode.tsx b/apps/web/src/components/ui/streaming/StreamingMessageCode/StreamingMessageCode.tsx index 3a5b61959..fb49ecb89 100644 --- a/apps/web/src/components/ui/streaming/StreamingMessageCode/StreamingMessageCode.tsx +++ b/apps/web/src/components/ui/streaming/StreamingMessageCode/StreamingMessageCode.tsx @@ -1,12 +1,12 @@ import pluralize from 'pluralize'; -import React, { useMemo, useState } from 'react'; +import React, { useMemo, useRef, useState } from 'react'; import { Text } from '@/components/ui/typography'; //THIS USED TO BE DYNAMIC... import { SyntaxHighlighter } from '@/components/ui/typography/SyntaxHighlight/SyntaxHighlighter'; import { useBusterNotifications } from '@/context/BusterNotifications'; import { Button } from '../../buttons'; import { FileCard } from '../../card/FileCard'; -import { Copy2 } from '../../icons'; +import { Check, Copy2 } from '../../icons'; type LineSegment = { type: 'text' | 'hidden'; @@ -25,10 +25,19 @@ export const StreamingMessageCode: React.FC<{ animation?: 'blur-in' | 'fade-in'; }> = React.memo(({ isStreamFinished, fileName, buttons, collapsible = false, text }) => { const { openSuccessMessage } = useBusterNotifications(); + const [hasCopied, setHasCopied] = useState(false); + const timeoutRef = useRef(null); const copyToClipboard = () => { navigator.clipboard.writeText(text); openSuccessMessage('Copied to clipboard'); + setHasCopied(true); + if (timeoutRef.current) { + clearTimeout(timeoutRef.current); + } + timeoutRef.current = setTimeout(() => { + setHasCopied(false); + }, 2500); }; const buttonComponent = useMemo(() => { @@ -39,14 +48,18 @@ export const StreamingMessageCode: React.FC<{ if (!buttons) { return (
-
); } return buttons; - }, [buttons]); + }, [buttons, hasCopied]); return ( diff --git a/apps/web/src/components/ui/typography/AnimatedMarkdown/animation-helpers.tsx b/apps/web/src/components/ui/typography/AnimatedMarkdown/animation-helpers.tsx index cab7fe54c..cb68be9f7 100644 --- a/apps/web/src/components/ui/typography/AnimatedMarkdown/animation-helpers.tsx +++ b/apps/web/src/components/ui/typography/AnimatedMarkdown/animation-helpers.tsx @@ -20,7 +20,15 @@ export const animateTokenizedText = ( if (typeof item === 'string') { return ; } else if (React.isValidElement(item)) { - const noAnimateElementTypes: React.ElementType[] = ['br', 'ul', 'ol', 'td', 'th']; + const noAnimateElementTypes: React.ElementType[] = [ + 'br', + 'ul', + 'ol', + 'td', + 'th', + 'pre', + 'code', + ]; const inlineElementTypes: React.ElementType[] = ['strong', 'em', 'del', 'code', 'a', 'span']; let typeName = item.type; diff --git a/apps/web/src/components/ui/typography/AnimatedMarkdown/useMarkdownComponents.tsx b/apps/web/src/components/ui/typography/AnimatedMarkdown/useMarkdownComponents.tsx index bd06734ef..7f66bebfa 100644 --- a/apps/web/src/components/ui/typography/AnimatedMarkdown/useMarkdownComponents.tsx +++ b/apps/web/src/components/ui/typography/AnimatedMarkdown/useMarkdownComponents.tsx @@ -34,7 +34,7 @@ interface UseMarkdownComponentsProps { export const useMarkdownComponents = ({ animation = 'blurIn', - animationDuration = 300, + animationDuration = 700, animationTimingFunction = 'ease-in-out', isStreamFinished = true, stripFormatting = true, diff --git a/apps/web/src/controllers/AppNoPageAccess.tsx b/apps/web/src/controllers/AppNoPageAccess.tsx index 61bf11050..2ff53ca54 100644 --- a/apps/web/src/controllers/AppNoPageAccess.tsx +++ b/apps/web/src/controllers/AppNoPageAccess.tsx @@ -14,8 +14,7 @@ export const AppNoPageAccess: React.FC<{ const isAnonymousUser = useIsAnonymousSupabaseUser(); const { buttonText, link } = useMemo(() => { - const isEmbedPage = - typeof window !== 'undefined' && window.location.pathname.startsWith('/embed'); + const isEmbedPage = window?.location.pathname.startsWith('/embed'); const shouldShowLogin = isAnonymousUser || isEmbedPage; diff --git a/apps/web/src/lib/isBrowser.ts b/apps/web/src/lib/isBrowser.ts index eee18805d..211a4a62b 100644 --- a/apps/web/src/lib/isBrowser.ts +++ b/apps/web/src/lib/isBrowser.ts @@ -1,7 +1,3 @@ -export const isBrowser = !!( - typeof window !== 'undefined' && - window.document && - window.document.createElement -); +export const isBrowser = !!window?.document?.createElement; export default isBrowser;