Priority: High | Effort: 4 hours | Dependencies: BRIEF-V2-001, BRIEF-V2-002
Add support for the new shortbio-final message from the backend and implement an engaging interim flow that displays key profile insights while users wait for the full briefing to complete.
- Add TypeScript interface for
shortbio-finalmessage structure - Update
useBriefingSearchV2hook to handle new message type - Add timing logic: show short bio cards only after 10 seconds AND when message arrives
- Implement card sequence: headline β uniqueQualifier β connectionValue β achievements β personalNote
- Each card displays for 7-10 seconds with CircularTimer component
- Chevron appears after 7 seconds, auto-advance at 10 seconds
- Smooth transitions between cards with proper loading states
- Cards use ContentCard component with ContentLabelChip for categories
- Profile confirmation shows for exactly 10 seconds before short bio cards begin
- Cards auto-advance smoothly without jarring transitions
- User can manually advance cards using chevron after 7 seconds
- Clear visual progression through the card sequence
- Graceful handling if shortbio-final message arrives before or after 10 seconds
- Cards stop and transition to full briefing when
formatted-finalmessage arrives
- No console errors during short bio flow
- Proper cleanup of timers when component unmounts or flow changes
- TypeScript strict mode compliance for all new interfaces
- Responsive design works on mobile and desktop
- Accessibility support for card navigation
// Add to useBriefingSearchV2.ts after existing message interfaces:
interface ShortBioFinalMessage {
kind: 'shortbio-final';
data: {
fullBio: string;
headline: string;
metadata: {
confidence: number;
dataCompleteness: number;
profileChallenges: string[];
};
currentRole: {
title: string;
duration: string;
organization: string;
};
achievements: string[];
personalNote: string;
profileQuality: 'complete' | 'limited';
connectionValue: string;
uniqueQualifier: string;
};
}
// Update the ResponseMessage union type:
type ResponseMessage =
| ProfileMessage
| BriefingIncrementalMessage
| BriefingFinalMessage
| FormattedFinalMessage
| ShortBioFinalMessage // Add this line
| ErrorMessage;
// Define card data structure:
interface ShortBioCard {
id: string;
label: string;
content: string;
duration: number; // seconds
}// Add these state variables to useBriefingSearchV2:
const [shortBioData, setShortBioData] = useState<ShortBioFinalMessage['data'] | null>(null);
const [showShortBioCards, setShowShortBioCards] = useState(false);
const [currentCardIndex, setCurrentCardIndex] = useState(0);
const [profileConfirmationStartTime, setProfileConfirmationStartTime] = useState<number | null>(null);
// Add timer ref for cleanup:
const shortBioTimerRef = useRef<NodeJS.Timeout | null>(null);// Update the processMessage function to handle shortbio-final:
const processMessage = (message: ResponseMessage) => {
setTimeout(() => {
switch (message.kind) {
case 'profile':
setProfileData(message.profile);
setSearchStatus(SearchStatus.LOADING);
setIsLoading(false);
// Start timing for short bio cards
setProfileConfirmationStartTime(Date.now());
break;
case 'shortbio-final':
setShortBioData(message.data);
// Check if 10 seconds have passed since profile confirmation
checkShortBioDisplayConditions();
break;
case 'formatted-final': {
// Stop short bio cards and show full briefing
setShowShortBioCards(false);
const sections = convertFormattedToSections(message.data);
setBriefingSections(sections);
setSearchStatus(SearchStatus.LOADED);
break;
}
// ... existing cases remain unchanged
}
}, 0);
};// Add these functions to the hook:
const checkShortBioDisplayConditions = useCallback(() => {
if (!shortBioData || !profileConfirmationStartTime) return;
const timeSinceConfirmation = Date.now() - profileConfirmationStartTime;
const tenSecondsElapsed = timeSinceConfirmation >= 10000;
if (tenSecondsElapsed) {
setShowShortBioCards(true);
setCurrentCardIndex(0);
} else {
// Wait for remaining time then show cards
const remainingTime = 10000 - timeSinceConfirmation;
shortBioTimerRef.current = setTimeout(() => {
setShowShortBioCards(true);
setCurrentCardIndex(0);
}, remainingTime);
}
}, [shortBioData, profileConfirmationStartTime]);
// Check conditions when shortBioData changes
useEffect(() => {
if (shortBioData) {
checkShortBioDisplayConditions();
}
}, [shortBioData, checkShortBioDisplayConditions]);
// Also check when 10 seconds pass (for cases where message arrives early)
useEffect(() => {
if (profileConfirmationStartTime && shortBioData) {
const timer = setTimeout(() => {
checkShortBioDisplayConditions();
}, 10000);
return () => clearTimeout(timer);
}
}, [profileConfirmationStartTime, shortBioData, checkShortBioDisplayConditions]);// Add this function to generate card data:
const generateShortBioCards = useCallback((data: ShortBioFinalMessage['data']): ShortBioCard[] => {
const cards: ShortBioCard[] = [
{
id: 'headline',
label: 'Profile Overview',
content: data.headline,
duration: 8,
},
{
id: 'unique-qualifier',
label: 'What Makes Them Unique',
content: data.uniqueQualifier,
duration: 9,
},
{
id: 'connection-value',
label: 'Why Connect',
content: data.connectionValue,
duration: 8,
},
];
// Add achievement cards
data.achievements.forEach((achievement, index) => {
cards.push({
id: `achievement-${index}`,
label: `Key Achievement ${index + 1}`,
content: achievement,
duration: 7,
});
});
// Add personal note card
cards.push({
id: 'personal-note',
label: 'Personal Insight',
content: data.personalNote,
duration: 8,
});
return cards;
}, []);// Add these functions for card navigation:
const advanceToNextCard = useCallback(() => {
if (!shortBioData) return;
const cards = generateShortBioCards(shortBioData);
const nextIndex = currentCardIndex + 1;
if (nextIndex >= cards.length) {
// Finished all cards, wait for full briefing or show loading
setShowShortBioCards(false);
} else {
setCurrentCardIndex(nextIndex);
}
}, [currentCardIndex, shortBioData, generateShortBioCards]);
const handleCardTimerComplete = useCallback(() => {
advanceToNextCard();
}, [advanceToNextCard]);// Add new state and functions to the hook's return:
return {
// ... all existing returns
shortBioData,
showShortBioCards,
currentCardIndex,
generateShortBioCards,
handleCardTimerComplete,
};// Update dismissSearch function to clean up short bio state:
const dismissSearch = useCallback(() => {
setIsDismissing(true);
// Cancel any ongoing streams
if (streamReaderRef.current.reader) {
streamReaderRef.current.reader.cancel();
}
if (streamReaderRef.current.controller) {
streamReaderRef.current.controller.abort();
}
// Clean up short bio timers
if (shortBioTimerRef.current) {
clearTimeout(shortBioTimerRef.current);
shortBioTimerRef.current = null;
}
// Reset all state including new short bio state
setSearchStatus(SearchStatus.WAITING_FOR_INPUT);
setCurrentStage(0);
setProfileData({
targetName: 'Loading Profile...',
headline: 'Please wait while we fetch the profile information',
});
setBriefingSections([]);
setDetailedBriefingNotes('');
setSubmissionError(null);
setIsLoading(false);
setIsRefreshing(false);
setSessionDetails(null);
setLastDismissalReason('user_dismissed');
// Reset short bio state
setShortBioData(null);
setShowShortBioCards(false);
setCurrentCardIndex(0);
setProfileConfirmationStartTime(null);
// Clear dismissing state after a brief delay for UI feedback
setTimeout(() => {
setIsDismissing(false);
}, 100);
}, []);// Add useEffect for component unmount cleanup:
useEffect(() => {
return () => {
if (shortBioTimerRef.current) {
clearTimeout(shortBioTimerRef.current);
}
};
}, []);// Add this function to BriefingPageV2 component:
const renderShortBioCards = () => {
if (!showShortBioCards || !shortBioData) return null;
const cards = generateShortBioCards(shortBioData);
const currentCard = cards[currentCardIndex];
if (!currentCard) return null;
return (
<div className="flex min-h-screen items-center justify-center px-4 bg-gradient-to-br from-cyan-50 to-cyan-100">
<div className="w-full max-w-md">
{/* Header with profile info */}
<div className="text-center mb-8">
<div className="flex items-center justify-center space-x-2 mb-2">
<div className="w-6 h-6 bg-cyan-600 rounded-full flex items-center justify-center">
<span className="text-white text-xs font-bold">π</span>
</div>
<span className="text-sm font-medium text-gray-600">
Preparing your brief about {profileData.targetName}
</span>
</div>
</div>
{/* Current Card */}
<ContentCard
label={currentCard.label}
content={currentCard.content}
duration={currentCard.duration}
onTimerComplete={handleCardTimerComplete}
/>
{/* Progress Indicator */}
<div className="mt-6 flex justify-center">
<div className="flex space-x-2">
{cards.map((_, index) => (
<div
key={index}
className={`w-2 h-2 rounded-full transition-colors ${
index === currentCardIndex
? 'bg-cyan-600'
: index < currentCardIndex
? 'bg-cyan-400'
: 'bg-gray-300'
}`}
/>
))}
</div>
</div>
{/* Minimise Button */}
<div className="text-center mt-8">
<button
onClick={dismissSearch}
className="text-cyan-600 text-base font-normal underline hover:text-cyan-800 transition-colors"
>
Minimise
</button>
</div>
</div>
</div>
);
};// Update the main component's return statement in BriefingPageV2:
return (
<BriefingErrorBoundary>
<div className="min-h-screen bg-white">
{renderUrlInput()}
{renderLoadingState()}
{renderProfileConfirmation()}
{renderShortBioCards()} {/* Add this line */}
{renderBriefingContent()}
</div>
</BriefingErrorBoundary>
);// Update renderProfileConfirmation to auto-dismiss after 10 seconds:
const renderProfileConfirmation = () => {
if (
searchStatus !== SearchStatus.LOADING ||
profileData.targetName === 'Loading Profile...' ||
!profileData.targetName.trim() ||
showShortBioCards // Don't show if short bio cards are active
) {
return null;
}
return (
<div className="flex min-h-screen items-center justify-center px-4">
<ConfirmCard
profileSrc={
profileData.profilePicUrl ||
`https://ui-avatars.com/api/?name=${encodeURIComponent(profileData.targetName)}&size=96&background=0891b2&color=fff`
}
name={profileData.targetName}
description={profileData.headline || 'LinkedIn Professional'}
buttonText="This isn't the right person"
onConfirm={dismissSearch}
duration={10} // Show for 10 seconds
onTimerComplete={() => {
// Timer complete - short bio cards should take over if ready
if (shortBioData) {
setShowShortBioCards(true);
setCurrentCardIndex(0);
}
}}
/>
</div>
);
};- Test
shortbio-finalmessage processing - Test 10-second timing logic works correctly
- Test card generation from short bio data
- Test card navigation and auto-advance
- Test timer cleanup on component unmount
- Test dismissal cleans up all short bio state
- Test complete flow from profile confirmation to short bio cards
- Test transition from short bio cards to full briefing
- Test early arrival of
shortbio-finalmessage (before 10 seconds) - Test late arrival of
shortbio-finalmessage (after 10 seconds) - Test dismissal during short bio card display
- Test
formatted-finalmessage interrupting short bio flow
- Test complete user journey with short bio flow
- Test card timing feels natural (7-10 seconds each)
- Test chevron manual advance works after 7 seconds
- Test progress indicator updates correctly
- Test responsive design on mobile and desktop
- Test dismissal works from any card in the sequence
- Timing Control: 10-second delay ensures users have time to confirm profile
- Card Duration: Variable timing (7-10 seconds) keeps content engaging
- Manual Advancement: Chevron appears after 7 seconds for user control
- Progress Indication: Dots show user's progress through card sequence
- Graceful Interruption: Full briefing can interrupt short bio cards
Profile Confirmation (10 seconds)
β (10 seconds elapsed + shortbio-final received)
Short Bio Cards (headline β unique β value β achievements β personal)
β (formatted-final message arrives)
Full Briefing Display
Scenario 1: Normal Flow
- Profile arrives β Start 10s timer
- shortbio-final arrives at 5s β Wait for timer
- Timer completes at 10s β Show cards
Scenario 2: Late Short Bio
- Profile arrives β Start 10s timer
- Timer completes at 10s β Wait for shortbio-final
- shortbio-final arrives at 12s β Show cards immediately
Scenario 3: Early Briefing
- Profile arrives β Start 10s timer
- shortbio-final arrives at 5s β Wait for timer
- Timer completes at 10s β Show cards
- formatted-final arrives during cards β Skip to briefing
- Don't forget to clean up timers on component unmount
- Don't show short bio cards if
formatted-finalhas already arrived - Don't let profile confirmation timer interfere with card timers
- Don't forget to handle edge cases (missing data, early completion)
- Don't forget responsive design for mobile card display
This story creates an engaging interim experience that keeps users invested while maintaining smooth transitions to the full briefing experience!