Files
cv-site/doc/13-TOAST-NOTIFICATIONS.md
T
juanatsap 9c5f0b20d8 docs: Add comprehensive toast notification test and documentation
Test Addition:
- Created tests/mjs/29-pdf-toast-notifications.test.mjs (22 assertions)
- Validates toast elements, JavaScript API, animations
- Tests both PDF download scenarios (modal stays open vs. closes early)
- Verifies toast lifecycle: prepare → progress → success → dismiss
- Tests icon/title/message updates and progress bar animation

Test Coverage:
 Toast DOM elements exist
 JavaScript functions available (showPDFToast, hidePDFToast)
 Manual toast trigger and auto-hide
 PDF modal integration - Scenario A (stay in modal)
 PDF modal integration - Scenario B (close modal, toast appears)
 Toast content updates (📥)
 Progress bar animation
 CSS animations defined (toastSlideIn, toastLifecycle, progressShrink)

Documentation Addition:
- Created doc/13-TOAST-NOTIFICATIONS.md (comprehensive guide)
- Architecture and component structure
- User experience flows (2 scenarios documented)
- JavaScript API reference (showPDFToast, hidePDFToast, showError)
- CSS structure and animations
- Template structure with ARIA accessibility
- PDF download integration details
- Accessibility features and testing guide
- Customization guide and troubleshooting

Documentation Updates:
- Updated doc/README.md (added entry #13, total: 12 → 13 docs)
- Updated tests/README.md (test count: 27 → 28)
- Updated tests/TEST-SUMMARY.md (added toast notifications coverage)
- Updated tests/mjs/README.md (added test 29 description)

Test Count: 28 active tests
Doc Count: 13 core documents
Coverage: Complete UX feedback system with dual-mode operation
2025-11-20 13:05:46 +00:00

19 KiB
Raw Blame History

Toast Notification System

Comprehensive documentation for the CV project's toast notification system.


Table of Contents

  1. Overview
  2. Architecture
  3. User Experience Flows
  4. Implementation Details
  5. JavaScript API
  6. CSS Structure
  7. Template Structure
  8. Integration with PDF Downloads
  9. Accessibility
  10. Testing
  11. Customization Guide
  12. Troubleshooting

Overview

The toast notification system provides non-intrusive visual feedback for asynchronous operations, particularly PDF generation and downloads. It enhances user experience by:

  • Immediate Feedback: Users know their action was registered
  • Progress Tracking: Visual indication of operation duration
  • Completion Confirmation: Clear success/error messaging
  • Non-Blocking UX: Users can continue working while operations complete

Key Features

Multiple toast variants (success, error, info, warning) Animated slide-in/slide-out Progress bar with timing animation Auto-dismiss with configurable duration Manual close button Mobile responsive (bottom-center on small screens) Accessibility compliant (ARIA labels, reduced motion support) Bilingual support (English/Spanish)


Architecture

Component Structure

Toast System
│
├── CSS Layer (static/css/04-interactive/_toasts.css)
│   ├── Toast container positioning
│   ├── Variant styles (success, error, info, warning)
│   ├── Animations (slide, fade, progress)
│   └── Responsive breakpoints
│
├── HTML Templates (templates/partials/widgets/)
│   ├── error-toast.html (Error notifications)
│   └── pdf-toast.html (PDF download feedback)
│
├── JavaScript API (static/js/main.js)
│   ├── showPDFToast(options)
│   ├── hidePDFToast()
│   └── showError(message)
│
└── Integration Layer
    └── PDF Modal (templates/partials/modals/pdf-modal.html)
        └── downloadPDF() function

Design Principles

  1. Progressive Enhancement: Works without JavaScript (fallback to modal only)
  2. Separation of Concerns: CSS handles animations, JS handles logic
  3. Reusability: Generic toast system extensible for any notification type
  4. Performance: Minimal DOM manipulation, CSS-driven animations
  5. Accessibility: Screen reader announcements, keyboard navigation

User Experience Flows

Flow 1: PDF Download (Modal Stays Open)

User clicks "Download PDF"
         ↓
Modal shows loading overlay
"⏳ Preparing PDF... (~4 seconds)"
         ↓
User waits in modal
         ↓
PDF downloads to browser
         ↓
Modal auto-closes
         ↓
DONE ✅

When to use: Default behavior for users who want to watch progress.

Flow 2: PDF Download (User Closes Modal)

User clicks "Download PDF"
         ↓
Modal shows loading overlay
         ↓
User presses ESC or clicks X
         ↓
Toast slides in (bottom-right)
"📥 Preparing PDF..."
"Generating Default CV (5 pages)... (~4s)"
[Progress bar animates]
         ↓
After estimated time (~4s)
         ↓
Toast updates
"✅ PDF Ready!"
"Check your downloads folder"
         ↓
Toast auto-dismisses (3s)
         ↓
DONE ✅

When to use: Power users who want to continue working while PDF generates.


Implementation Details

File Structure

cv/
├── static/css/04-interactive/
│   └── _toasts.css                    # Toast styles (242 lines)
├── templates/partials/widgets/
│   ├── error-toast.html               # Error notification template
│   └── pdf-toast.html                 # PDF download notification template
├── templates/index.html               # Includes toast templates
├── static/js/main.js                  # Toast functions
└── templates/partials/modals/
    └── pdf-modal.html                 # Integrates toast with downloads

CSS Organization

File: static/css/04-interactive/_toasts.css

Sections:

  1. Base Toast Container - Positioning, layout, common styles
  2. Toast Variants - Color schemes for different notification types
  3. Content Elements - Icon, title, message, close button
  4. Progress Bar - Visual countdown indicator
  5. Animations - Slide-in, slide-out, lifecycle, progress
  6. Responsive Design - Mobile breakpoints
  7. Accessibility - Reduced motion preferences
  8. Print Styles - Hide toasts in print media

JavaScript API

showPDFToast(options)

Display a PDF download toast notification.

Parameters:

{
  icon: string,        // Emoji icon (📥, ✅, ⚠️)
  title: string,       // Toast title
  message: string,     // Toast message
  duration: number,    // Auto-hide duration in ms (default: 5000)
  autoHide: boolean    // Auto-dismiss after duration (default: true)
}

Example:

window.showPDFToast({
  icon: '📥',
  title: 'Preparing PDF...',
  message: 'Generating Default CV (5 pages)... (~4s)',
  duration: 4000,
  autoHide: false  // Manually control dismissal
});

Return: None

Side Effects:

  • Adds .show class to toast element
  • Starts progress bar animation
  • Schedules auto-hide timeout (if autoHide: true)

hidePDFToast()

Immediately hide the PDF toast.

Parameters: None

Example:

window.hidePDFToast();

Return: None

Side Effects:

  • Removes .show class from toast element
  • Cancels progress bar animation

showError(message)

Display an error toast notification.

Parameters:

message: string  // Error message to display

Example:

window.showError('Failed to load content. Please try again.');

Return: None

Side Effects:

  • Shows error toast with red styling
  • Auto-hides after 5 seconds (CSS animation)

CSS Structure

Toast Variants

Success Toast (Green)

.success-toast {
  background: linear-gradient(135deg,
    rgba(40, 167, 69, 0.95) 0%,
    rgba(25, 135, 84, 0.95) 100%);
  color: white;
  border-left: 4px solid #fff;
}

Error Toast (Red)

.error-toast {
  background: linear-gradient(135deg,
    rgba(220, 53, 69, 0.95) 0%,
    rgba(200, 35, 51, 0.95) 100%);
  color: white;
  border-left: 4px solid #fff;
}

Info Toast (Blue)

.info-toast {
  background: linear-gradient(135deg,
    rgba(13, 110, 253, 0.95) 0%,
    rgba(10, 88, 202, 0.95) 100%);
  color: white;
  border-left: 4px solid #fff;
}

Warning Toast (Yellow)

.warning-toast {
  background: linear-gradient(135deg,
    rgba(255, 193, 7, 0.95) 0%,
    rgba(255, 167, 38, 0.95) 100%);
  color: #333;
  border-left: 4px solid #333;
}

Key Animations

Slide In

@keyframes toastSlideIn {
  from {
    opacity: 0;
    transform: translateX(100%) translateY(0);
  }
  to {
    opacity: 1;
    transform: translateX(0) translateY(0);
  }
}

Lifecycle (Auto-hide)

@keyframes toastLifecycle {
  0% {
    opacity: 1;
    transform: translateX(0) translateY(0);
  }
  85% {
    opacity: 1;
    transform: translateX(0) translateY(0);
  }
  100% {
    opacity: 0;
    transform: translateX(100%) translateY(0);
  }
}

Progress Bar

@keyframes progressShrink {
  from { width: 100%; }
  to { width: 0%; }
}

Template Structure

PDF Toast Template

File: templates/partials/widgets/pdf-toast.html

<div id="pdf-toast" class="success-toast no-print"
     role="status" aria-live="polite" aria-atomic="true">

  <!-- Icon -->
  <span class="toast-icon" id="pdf-toast-icon">📥</span>

  <!-- Content -->
  <div class="toast-content">
    <p class="toast-title" id="pdf-toast-title">Preparing PDF</p>
    <p class="toast-message" id="pdf-toast-message"></p>
  </div>

  <!-- Close Button -->
  <button aria-label="Close notification"
          class="toast-close"
          onclick="document.getElementById('pdf-toast').classList.remove('show')">
    ×
  </button>

  <!-- Progress Bar -->
  <div class="toast-progress">
    <div class="toast-progress-bar" id="pdf-toast-progress"></div>
  </div>
</div>

Key Elements

  • role="status": ARIA role for status messages
  • aria-live="polite": Screen reader announcement (non-interrupting)
  • aria-atomic="true": Read entire toast as single unit
  • .no-print: Hidden in print media
  • #pdf-toast-icon: Dynamically updated emoji
  • #pdf-toast-title: Main heading
  • #pdf-toast-message: Detailed message
  • .toast-progress-bar: Visual countdown indicator

Integration with PDF Downloads

Download Flow Integration

File: templates/partials/modals/pdf-modal.html

function downloadPDF() {
  // ... setup code ...

  // Track if modal is closed by user
  let modalClosedByUser = false;

  const onModalClose = () => {
    modalClosedByUser = true;

    // Show toast when user closes modal
    if (window.showPDFToast) {
      window.showPDFToast({
        icon: '📥',
        title: isSpanish ? 'Preparando PDF...' : 'Preparing PDF...',
        message: `Generating ${formatName}... (~${estimatedTime}s)`,
        duration: estimatedTime * 1000,
        autoHide: false
      });
    }
  };

  // Listen for modal close
  modal.addEventListener('close', onModalClose, { once: true });

  // Trigger download
  window.location.href = url;

  // After estimated time: update toast or close modal
  setTimeout(() => {
    if (modalClosedByUser && window.showPDFToast) {
      // Update toast to success
      window.showPDFToast({
        icon: '✅',
        title: isSpanish ? '¡PDF Listo!' : 'PDF Ready!',
        message: isSpanish
          ? 'Revisa tu carpeta de descargas'
          : 'Check your downloads folder',
        duration: 3000,
        autoHide: true
      });
    } else {
      // Close modal if still open
      modal.close();
    }
  }, estimatedTime * 1000);
}

Format-Specific Timing

PDF Format Pages Estimated Time Icon Progression
Short 4 3 seconds 📥
Default 5 4 seconds 📥
Extended 9 8 seconds 📥

Accessibility

ARIA Attributes

<div id="pdf-toast"
     role="status"           <!-- Status message role -->
     aria-live="polite"      <!-- Non-interrupting announcement -->
     aria-atomic="true">     <!-- Read as single unit -->

Screen Reader Behavior

  1. Toast Appears: Screen reader announces title + message
  2. Toast Updates: New content announced automatically
  3. Toast Dismisses: Silent (user already informed)

Keyboard Navigation

  • Close Button: Focusable with Tab
  • Enter/Space: Activate close button
  • ESC: Closes parent modal (which may trigger toast)

Reduced Motion

Users with motion sensitivity get simplified animations:

@media (prefers-reduced-motion: reduce) {
  .success-toast,
  .error-toast,
  .toast {
    animation: none;
    opacity: 1;
  }

  .toast-progress-bar {
    animation: none;
  }
}

Effect: Toast appears/disappears instantly without sliding/fading.


Testing

Automated Test

File: tests/mjs/29-pdf-toast-notifications.test.mjs

Coverage:

  • Toast elements exist in DOM
  • Toast initially hidden
  • JavaScript functions available
  • Manual toast trigger works
  • Toast auto-hides after duration
  • PDF modal integration (Scenario A: Stay in modal)
  • PDF modal integration (Scenario B: Close modal early)
  • CSS animations defined
  • Icon/title/message updates
  • Progress bar animation

Run Test:

bun tests/mjs/29-pdf-toast-notifications.test.mjs

Manual Testing Checklist

Scenario 1: Modal Overlay Only

  • Open PDF modal
  • Select Default CV
  • Click Download PDF
  • Wait in modal without closing
  • Verify overlay shows with spinner
  • Verify modal closes after ~4 seconds
  • Verify NO toast appears

Scenario 2: Toast After Modal Close

  • Open PDF modal
  • Select Extended CV (9 pages)
  • Click Download PDF
  • Immediately press ESC to close modal
  • Verify toast appears bottom-right
  • Verify icon is 📥
  • Verify title says "Preparing PDF..."
  • Verify message includes format name and time
  • Verify progress bar animates (shrinks)
  • Wait ~8 seconds
  • Verify toast updates to
  • Verify title says "PDF Ready!"
  • Verify toast auto-dismisses after ~3 seconds

Scenario 3: Close Button

  • Trigger toast manually (browser console): showPDFToast({icon: '🧪', title: 'Test', message: 'Test message'})
  • Click × close button
  • Verify toast disappears immediately

Scenario 4: Mobile Responsive

  • Resize browser to < 540px width
  • Trigger toast
  • Verify toast is full-width at bottom
  • Verify content is readable
  • Verify close button accessible

Customization Guide

Adding a New Toast Variant

1. Define CSS Class (static/css/04-interactive/_toasts.css):

.custom-toast {
  background: linear-gradient(135deg,
    rgba(156, 39, 176, 0.95) 0%,
    rgba(123, 31, 162, 0.95) 100%);
  color: white;
  border-left: 4px solid #fff;
}

2. Create HTML Template (templates/partials/widgets/custom-toast.html):

{{define "custom-toast"}}
<div id="custom-toast" class="custom-toast no-print"
     role="status" aria-live="polite">
  <span class="toast-icon" id="custom-toast-icon">🎨</span>
  <div class="toast-content">
    <p class="toast-title" id="custom-toast-title">Custom Title</p>
    <p class="toast-message" id="custom-toast-message"></p>
  </div>
  <button class="toast-close">×</button>
</div>
{{end}}

3. Add JavaScript Function (static/js/main.js):

window.showCustomToast = function(options = {}) {
  const toast = document.getElementById('custom-toast');
  const icon = document.getElementById('custom-toast-icon');
  const title = document.getElementById('custom-toast-title');
  const message = document.getElementById('custom-toast-message');

  if (options.icon) icon.textContent = options.icon;
  if (options.title) title.textContent = options.title;
  if (options.message) message.textContent = options.message;

  toast.classList.remove('show');
  void toast.offsetWidth;
  toast.classList.add('show');

  if (options.autoHide !== false) {
    const duration = options.duration || 5000;
    setTimeout(() => toast.classList.remove('show'), duration);
  }
};

4. Include in Index (templates/index.html):

{{template "custom-toast" .}}

Changing Animation Duration

CSS (_toasts.css):

/* Change lifecycle duration (default: 5s) */
.toast.show {
  animation: toastLifecycle 8s ease forwards; /* Now 8 seconds */
}

/* Change progress bar duration */
.toast-progress-bar {
  animation: progressShrink 8s linear forwards; /* Match lifecycle */
}

JavaScript (when calling function):

window.showPDFToast({
  icon: '📥',
  title: 'Preparing...',
  message: 'This will take longer',
  duration: 10000  // 10 seconds
});

Troubleshooting

Toast Not Appearing

Symptom: Calling showPDFToast() has no effect.

Checks:

  1. Verify toast element exists: document.getElementById('pdf-toast')
  2. Check console for JavaScript errors
  3. Verify CSS is loaded: Inspect element and check for .success-toast styles
  4. Confirm toasts.css imported in main.css
  5. Check index.html includes {{template "pdf-toast" .}}

Solution:

# Verify CSS import
grep "toasts.css" static/css/main.css

# Verify template inclusion
grep "pdf-toast" templates/index.html

# Check browser console
# Should see no errors when calling showPDFToast()

Toast Not Auto-Dismissing

Symptom: Toast appears but never disappears.

Checks:

  1. Verify autoHide is not set to false
  2. Check CSS animation: toastLifecycle should be defined
  3. Inspect element: animation property should be set
  4. Check for prefers-reduced-motion (disables animations)

Solution:

// Explicitly set autoHide
window.showPDFToast({
  icon: '✅',
  title: 'Test',
  message: 'Should auto-hide',
  duration: 3000,
  autoHide: true  // Explicitly enable
});

// Or manually hide
setTimeout(() => window.hidePDFToast(), 3000);

Progress Bar Not Animating

Symptom: Progress bar appears but doesn't shrink.

Checks:

  1. Verify progressShrink animation is defined in CSS
  2. Check animation is applied: Inspect #pdf-toast-progress
  3. Confirm prefers-reduced-motion is not active
  4. Check duration parameter matches animation duration

Solution:

// Ensure progress bar animation resets
const progressBar = document.getElementById('pdf-toast-progress');
if (progressBar) {
  progressBar.style.animation = 'none';
  void progressBar.offsetWidth; // Trigger reflow
  progressBar.style.animation = 'progressShrink 5000ms linear forwards';
}

Toast Overlaps Content on Mobile

Symptom: Toast covers important content on small screens.

Checks:

  1. Verify responsive CSS is loaded
  2. Check viewport width: < 540px should trigger mobile styles
  3. Inspect z-index conflicts

Solution:

/* Adjust mobile positioning */
@media (max-width: 540px) {
  .toast {
    bottom: 1rem;  /* Lower if needed */
    right: 1rem;
    left: 1rem;
    z-index: 10000; /* Ensure on top */
  }
}

Multiple Toasts Stacking

Symptom: Calling showPDFToast() multiple times creates overlapping toasts.

Root Cause: Current system uses single toast element, so multiple calls update the same toast.

Expected Behavior: Later calls replace earlier toast content.

If You Need Multiple Toasts:

  1. Clone toast element before showing:
function showMultipleToasts(options) {
  const original = document.getElementById('pdf-toast');
  const clone = original.cloneNode(true);
  clone.id = `toast-${Date.now()}`;
  document.body.appendChild(clone);

  // Update clone content and show
  // ... similar logic to showPDFToast ...
}
  1. Or use a toast queue system (advanced).


Last Updated: 2025-11-20 Version: 1.0 Status: Production Ready Test Coverage: 29-pdf-toast-notifications.test.mjs (22 assertions)