Skip to content

UX Design: Single Message Processing Workflow

Date: 2025-10-01 Component: ApplicationPage.tsx - Processing Step Designer: UX Designer Agent Status: Design Approved - Ready for Implementation


Executive Summary

Replace the current stacked multi-message agent cards with a single dynamic message container that smoothly transitions between agent states, providing a cleaner, more focused user experience during loan processing.

Key Improvements: - Reduce visual clutter from 4 stacked cards to 1 dynamic container - Add smooth fade transitions between agent messages - Include handoff messaging to explain workflow progression - Maintain clear progress communication throughout 6-second workflow


User Experience Flow

Current State Problems

  1. Visual Clutter: 4 message cards stacking creates overwhelming UI
  2. Lost Focus: User attention divided across multiple messages
  3. No Handoff Context: Abrupt jumps between agent completions
  4. Screen Real Estate: Unnecessary scrolling on smaller screens

Proposed Solution Benefits

  1. Single Focus Point: One message container keeps user attention centered
  2. Smooth Transitions: Fade animations create polished, professional feel
  3. Clear Handoffs: Explicit "handing over" messages explain workflow
  4. Better Mobile UX: No scrolling, consistent positioning

State Machine Design

Message States (5 states per agent)

Agent Workflow Cycle:
1. PROCESSING β†’ Agent is actively working
2. COMPLETE β†’ Agent finished their analysis
3. TRANSITION β†’ "Handing over to [Next Agent]..."
4. FADE_OUT β†’ Previous message fading out
5. FADE_IN β†’ Next agent's message fading in

Complete State Timeline (6 seconds)

Time | Progress | State           | Agent      | Message Type
-----|----------|-----------------|------------|------------------
0.0s | 25%      | COMPLETE        | Intake     | Completion message
0.5s | 25%      | TRANSITION      | Intake     | "Handing over to Sarah..."
1.0s | 25%β†’50%  | FADE_OUT/IN     | Credit     | "Sarah is analyzing..."
2.0s | 50%      | COMPLETE        | Credit     | Completion message
2.5s | 50%      | TRANSITION      | Credit     | "Handing over to Marcus..."
3.0s | 50%β†’75%  | FADE_OUT/IN     | Income     | "Marcus is verifying..."
4.0s | 75%      | COMPLETE        | Income     | Completion message
4.5s | 75%      | TRANSITION      | Income     | "Handing over to Alex..."
5.0s | 75%β†’100% | FADE_OUT/IN     | Risk       | "Alex is assessing..."
6.0s | 100%     | COMPLETE        | Risk       | Final completion message

Message Copy Specification

Agent 1: Cap-ital America (Intake Agent)

PROCESSING (0.0s - shown immediately when processing starts):

πŸ¦Έβ€β™‚οΈ Cap-ital America is reviewing your application...

COMPLETE (0.0s - immediately replace PROCESSING):

βœ… Application validated! All information looks great.

TRANSITION (0.5s delay):

🀝 Handing over to Sarah for credit analysis...

Agent 2: Sarah (Credit Analyst)

PROCESSING (1.0s - fade in):

πŸ¦Έβ€β™€οΈ Sarah is analyzing your credit profile...

COMPLETE (2.0s):

βœ… Credit profile looks strong! Good payment history detected.

TRANSITION (2.5s delay):

🀝 Handing over to Marcus for income verification...

Agent 3: Marcus (Income Verifier)

PROCESSING (3.0s - fade in):

🦸 Marcus is verifying your income and employment...

COMPLETE (4.0s):

βœ… Income verified! Debt-to-income ratio is healthy.

TRANSITION (4.5s delay):

🀝 Handing over to Alex for risk assessment...

Agent 4: Alex (Risk Assessor)

PROCESSING (5.0s - fade in):

πŸ¦Ήβ€β™‚οΈ Alex is performing final risk assessment...

COMPLETE (6.0s):

βœ… Risk assessment complete! All metrics look excellent.
πŸŽ‰ Your application has been APPROVED!


Animation Specifications

Timing Constants

const ANIMATION_TIMING = {
  COMPLETE_DISPLAY: 500,      // How long completion message shows (0.5s)
  TRANSITION_DISPLAY: 500,    // How long handoff message shows (0.5s)
  FADE_OUT_DURATION: 200,     // Fade out animation time (0.2s)
  FADE_IN_DURATION: 300,      // Fade in animation time (0.3s)
  PROCESSING_MIN_TIME: 1000,  // Minimum time showing "processing..." (1s)
} as const;

CSS Animation Classes

/* Fade Out Animation */
.message-fade-out {
  animation: fadeOut 200ms ease-out forwards;
  opacity: 0;
}

@keyframes fadeOut {
  from { opacity: 1; transform: translateY(0); }
  to { opacity: 0; transform: translateY(-10px); }
}

/* Fade In Animation */
.message-fade-in {
  animation: fadeIn 300ms ease-in forwards;
  opacity: 1;
}

@keyframes fadeIn {
  from { opacity: 0; transform: translateY(10px); }
  to { opacity: 1; transform: translateY(0); }
}

/* Pulse for Processing State */
.message-pulse {
  animation: pulse 2s ease-in-out infinite;
}

@keyframes pulse {
  0%, 100% { opacity: 1; }
  50% { opacity: 0.7; }
}

Transition Behavior

  1. Completion β†’ Transition: Instant replacement (no animation)
  2. Transition β†’ Processing: Fade out (200ms) β†’ Fade in (300ms)
  3. Processing β†’ Complete: Instant replacement (no animation)

Component Structure

Message Container Layout

<div className="single-message-container">
  {/* Icon Badge */}
  <div className="agent-icon-badge">
    <span className="text-2xl">{currentMessage.icon}</span>
  </div>

  {/* Message Content */}
  <div className="message-content">
    <p className="agent-name">{currentMessage.agentName}</p>
    <p className="message-text">{currentMessage.text}</p>
  </div>

  {/* Status Indicator (optional) */}
  {currentMessage.isProcessing && (
    <div className="processing-spinner">
      {/* Animated spinner or dots */}
    </div>
  )}
</div>

Visual Styling

// Container (replaces lines 444-475 in ApplicationPage.tsx)
<div className="mt-10">
  <h3 className="text-lg font-semibold text-center mb-4">
    <span className="bg-gradient-to-r from-brand-600 via-accent-600 to-brand-500 bg-clip-text text-transparent">
      πŸ’¬ What Your AI Team Says
    </span>
  </h3>

  {/* Single Message Container */}
  <div
    className={`
      bg-gradient-to-r from-gray-50 to-blue-50
      dark:from-dark-bg-tertiary dark:to-dark-bg-card
      rounded-lg p-6 border border-gray-200
      dark:border-gray-700 shadow-md
      min-h-[120px] flex items-center
      transition-all duration-300
      ${animationClass}
    `}
  >
    <div className="flex items-start space-x-4 w-full">
      {/* Agent Icon */}
      <div className="w-12 h-12 bg-gradient-to-br from-brand-500 to-brand-600 rounded-full flex items-center justify-center flex-shrink-0">
        <span className="text-2xl">{currentMessage.icon}</span>
      </div>

      {/* Message Content */}
      <div className="flex-1">
        <p className="font-semibold text-gray-900 dark:text-dark-text-primary text-sm mb-2">
          {currentMessage.agentName}
        </p>
        <p className="text-gray-700 dark:text-dark-text-secondary text-base leading-relaxed">
          {currentMessage.text}
        </p>
      </div>

      {/* Processing Indicator (show only during PROCESSING state) */}
      {currentMessage.state === 'PROCESSING' && (
        <div className="flex space-x-1">
          <div className="w-2 h-2 bg-brand-500 rounded-full animate-bounce"></div>
          <div className="w-2 h-2 bg-brand-500 rounded-full animate-bounce delay-100"></div>
          <div className="w-2 h-2 bg-brand-500 rounded-full animate-bounce delay-200"></div>
        </div>
      )}
    </div>
  </div>
</div>

State Management Pattern

Message State Type

interface AgentMessage {
  agentName: string;
  icon: string;
  text: string;
  state: 'PROCESSING' | 'COMPLETE' | 'TRANSITION';
  progress: number;
}

interface MessageSequence {
  id: string;
  agentName: string;
  icon: string;
  processingText: string;
  completeText: string;
  transitionText?: string; // For all except last agent
  progressStart: number;
  progressEnd: number;
  timing: {
    processingDelay: number;
    completeDuration: number;
    transitionDuration: number;
  };
}

React State Implementation

const [currentMessage, setCurrentMessage] = useState<AgentMessage>({
  agentName: 'Cap-ital America',
  icon: 'πŸ¦Έβ€β™‚οΈ',
  text: 'Application received and validated. All information looks great.',
  state: 'COMPLETE',
  progress: 25
});

const [animationClass, setAnimationClass] = useState<string>('');

Message Sequence Configuration

const MESSAGE_SEQUENCE: MessageSequence[] = [
  {
    id: 'intake',
    agentName: 'Cap-ital America',
    icon: 'πŸ¦Έβ€β™‚οΈ',
    processingText: 'Cap-ital America is reviewing your application...',
    completeText: 'βœ… Application validated! All information looks great.',
    transitionText: '🀝 Handing over to Sarah for credit analysis...',
    progressStart: 0,
    progressEnd: 25,
    timing: {
      processingDelay: 0,        // Show immediately
      completeDuration: 500,     // Show completion for 0.5s
      transitionDuration: 500    // Show transition for 0.5s
    }
  },
  {
    id: 'credit',
    agentName: 'Sarah (Credit Analyst)',
    icon: 'πŸ¦Έβ€β™€οΈ',
    processingText: 'Sarah is analyzing your credit profile...',
    completeText: 'βœ… Credit profile looks strong! Good payment history detected.',
    transitionText: '🀝 Handing over to Marcus for income verification...',
    progressStart: 25,
    progressEnd: 50,
    timing: {
      processingDelay: 1000,     // Start after 1s
      completeDuration: 500,
      transitionDuration: 500
    }
  },
  {
    id: 'income',
    agentName: 'Marcus (Income Verifier)',
    icon: '🦸',
    processingText: 'Marcus is verifying your income and employment...',
    completeText: 'βœ… Income verified! Debt-to-income ratio is healthy.',
    transitionText: '🀝 Handing over to Alex for risk assessment...',
    progressStart: 50,
    progressEnd: 75,
    timing: {
      processingDelay: 3000,     // Start after 3s
      completeDuration: 500,
      transitionDuration: 500
    }
  },
  {
    id: 'risk',
    agentName: 'Alex (Risk Assessor)',
    icon: 'πŸ¦Ήβ€β™‚οΈ',
    processingText: 'Alex is performing final risk assessment...',
    completeText: 'βœ… Risk assessment complete! All metrics look excellent.\nπŸŽ‰ Your application has been APPROVED!',
    transitionText: undefined,  // No transition - this is the final agent
    progressStart: 75,
    progressEnd: 100,
    timing: {
      processingDelay: 5000,     // Start after 5s
      completeDuration: 2000,    // Show final message longer (2s)
      transitionDuration: 0
    }
  }
];

useEffect Logic

useEffect(() => {
  if (currentStep !== 'processing') {
    return;
  }

  const timers: NodeJS.Timeout[] = [];
  let cumulativeDelay = 0;

  MESSAGE_SEQUENCE.forEach((sequence, index) => {
    // 1. Show PROCESSING state
    timers.push(setTimeout(() => {
      setAnimationClass('message-fade-in');
      setCurrentMessage({
        agentName: sequence.agentName,
        icon: sequence.icon,
        text: sequence.processingText,
        state: 'PROCESSING',
        progress: sequence.progressStart
      });
      setProcessingProgress(sequence.progressStart);

      // Remove animation class after animation completes
      setTimeout(() => setAnimationClass(''), 300);
    }, sequence.timing.processingDelay));

    cumulativeDelay = sequence.timing.processingDelay + sequence.timing.completeDuration;

    // 2. Show COMPLETE state (after processing finishes)
    timers.push(setTimeout(() => {
      setCurrentMessage({
        agentName: sequence.agentName,
        icon: sequence.icon,
        text: sequence.completeText,
        state: 'COMPLETE',
        progress: sequence.progressEnd
      });
      setProcessingProgress(sequence.progressEnd);
    }, cumulativeDelay));

    // 3. Show TRANSITION state (if not last agent)
    if (sequence.transitionText && index < MESSAGE_SEQUENCE.length - 1) {
      cumulativeDelay += sequence.timing.completeDuration;

      timers.push(setTimeout(() => {
        setCurrentMessage({
          agentName: sequence.agentName,
          icon: '🀝',
          text: sequence.transitionText!,
          state: 'TRANSITION',
          progress: sequence.progressEnd
        });
      }, cumulativeDelay));

      // 4. Trigger fade-out before next agent
      cumulativeDelay += sequence.timing.transitionDuration - 200; // Start fade 200ms before next

      timers.push(setTimeout(() => {
        setAnimationClass('message-fade-out');
      }, cumulativeDelay));
    }
  });

  return () => timers.forEach(clearTimeout);
}, [currentStep]);

Accessibility Implementation

ARIA Live Regions

<div
  role="status"
  aria-live="polite"
  aria-atomic="true"
  className="single-message-container"
>
  {/* Message content */}
</div>

Why aria-live="polite": - Updates announced by screen readers when user is idle - Doesn't interrupt current reading - Appropriate for progress updates that aren't critical alerts

Why aria-atomic="true": - Screen reader reads entire message on each update - Prevents partial/confusing updates - User gets complete context every time

Screen Reader Announcements

// Enhanced message with screen reader text
interface AccessibleMessage extends AgentMessage {
  ariaLabel: string; // Full context for screen readers
}

// Example implementation
const accessibleMessage: AccessibleMessage = {
  agentName: 'Sarah (Credit Analyst)',
  icon: 'πŸ¦Έβ€β™€οΈ',
  text: 'Credit profile looks strong! Good payment history detected.',
  state: 'COMPLETE',
  progress: 50,
  ariaLabel: 'Credit analysis complete. Sarah says: Credit profile looks strong! Good payment history detected. Progress: 50 percent complete.'
};

Keyboard Navigation

{/* Ensure container is not focusable (it's status only, not interactive) */}
<div
  className="single-message-container"
  role="status"
  aria-live="polite"
  tabIndex={-1}
>
  {/* Content */}
</div>

Reduced Motion Support

/* Respect user's motion preferences */
@media (prefers-reduced-motion: reduce) {
  .message-fade-out,
  .message-fade-in {
    animation: none;
    transition: opacity 0ms;
  }

  .message-pulse {
    animation: none;
  }
}

Color Contrast Compliance

WCAG AA Compliance: - Text color: text-gray-900 (contrast ratio > 7:1 on white background) - Agent name: text-gray-900 font-semibold (enhanced contrast) - Icon background: Sufficient contrast with icon emoji - Border: Visible in both light and dark modes


Mobile Responsiveness

Container Sizing

{/* Responsive container with min-height for content stability */}
<div className="
  min-h-[100px] sm:min-h-[120px]
  p-4 sm:p-6
  rounded-lg
">
  {/* Content */}
</div>

Icon Sizing

{/* Responsive icon sizing */}
<div className="
  w-10 h-10 sm:w-12 sm:h-12
  rounded-full
  flex items-center justify-center
">
  <span className="text-xl sm:text-2xl">
    {currentMessage.icon}
  </span>
</div>

Text Sizing

{/* Responsive text sizing */}
<p className="
  text-sm sm:text-base
  leading-relaxed
">
  {currentMessage.text}
</p>

Testing Checklist

Visual Testing

  • Message transitions smoothly between states
  • No visual "jump" or layout shift during transitions
  • Icons display correctly in all states
  • Dark mode renders properly
  • Mobile layout doesn't overflow or wrap awkwardly

Timing Testing

  • PROCESSING state shows for minimum 1 second
  • COMPLETE state displays for 0.5 seconds
  • TRANSITION message visible for 0.5 seconds
  • Total workflow completes in ~6 seconds
  • Progress bar syncs with message updates

Accessibility Testing

  • Screen reader announces each message update
  • Full message context provided (not just partial updates)
  • No interruption during user interaction
  • Reduced motion preference respected
  • Color contrast meets WCAG AA standards

Edge Case Testing

  • Rapid navigation away during processing
  • Browser tab backgrounded during workflow
  • Network interruption during processing
  • Multiple rapid clicks on "Submit Application"

Performance Considerations

Memory Management

// Always cleanup timers in useEffect return
useEffect(() => {
  const timers: NodeJS.Timeout[] = [];

  // ... setup timers ...

  return () => {
    timers.forEach(clearTimeout); // Prevent memory leaks
  };
}, [currentStep]);

Animation Performance

/* Use transform and opacity for GPU acceleration */
@keyframes fadeIn {
  from {
    opacity: 0;
    transform: translateY(10px); /* GPU-accelerated */
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* Avoid animating layout properties like height, width, margin */

Render Optimization

// Memoize message sequence to prevent recalculation
const messageSequence = useMemo(() => MESSAGE_SEQUENCE, []);

// Only update DOM when message actually changes
const messageKey = `${currentMessage.agentName}-${currentMessage.state}`;

Implementation Roadmap

Phase 1: Core Implementation (Est. 2-3 hours)

  1. Create message sequence configuration
  2. Implement state machine in useEffect
  3. Add basic fade animations
  4. Update component structure

Phase 2: Polish & Accessibility (Est. 1-2 hours)

  1. Add ARIA attributes
  2. Implement screen reader support
  3. Add reduced motion support
  4. Test keyboard navigation

Phase 3: Testing & Refinement (Est. 1 hour)

  1. Cross-browser testing
  2. Mobile responsiveness validation
  3. Accessibility audit with screen reader
  4. Performance profiling

Total Estimated Time: 4-6 hours


Success Metrics

User Experience

  • Reduced cognitive load: Single message vs. 4 stacked cards
  • Improved comprehension: Clear handoff messages explain workflow
  • Better engagement: Smooth animations feel more polished

Technical

  • Performance: No frame drops during animations (<16ms per frame)
  • Accessibility: WCAG AA compliance (color contrast, screen reader support)
  • Mobile: No layout shifts on devices 320px-1920px wide

Business

  • Reduced abandonment: Cleaner UI keeps users engaged during processing
  • Increased trust: Professional animations convey reliability
  • Better conversion: Clear progress communication reduces anxiety

  • Current Implementation: /workspaces/loan-defenders/loan_defenders/ui/src/pages/application/ApplicationPage.tsx (lines 443-475)
  • Type Definitions: /workspaces/loan-defenders/loan_defenders/ui/src/types/index.ts
  • Animation Patterns: See Tailwind CSS utilities in existing codebase

Approval & Sign-off

UX Designer: Approved - Design ready for implementation Responsible AI Agent: Pending - Accessibility review needed Code Reviewer Agent: Pending - Implementation review after coding complete

Next Steps: Share with Product Manager agent for business alignment validation, then proceed with implementation.