5.8 KiB
5.8 KiB
26. Polish — Error Boundaries, Loading States, Skeletons, and Transitions
meta: id: shieldai-unified-restructure-26 feature: shieldai-unified-restructure priority: P1 depends_on: [shieldai-unified-restructure-24, shieldai-unified-restructure-25] tags: [frontend, polish, ux, accessibility, solidjs]
objective:
- Add production polish to the web app: error boundaries for graceful failure handling, skeleton screens for loading states, smooth page transitions, and comprehensive accessibility improvements.
deliverables:
web/src/components/ui/ErrorBoundary.tsx— Global error boundary:- Catches runtime errors in child components
- Displays friendly error page with ShieldAI branding
- "Try again" button to reset error boundary
- "Report issue" button (placeholder for Sentry integration)
- Logs error details to console (and eventually monitoring)
web/src/components/ui/Skeleton.tsx— Skeleton loading components:SkeletonText— animated pulse lines for text placeholdersSkeletonCard— card-shaped skeleton with header, body, footerSkeletonAvatar— circular skeleton for profile imagesSkeletonTable— table row skeletons- All use
.bg-bg-tertiarywith shimmer animation
web/src/components/ui/PageTransition.tsx— Page transition wrapper:- Fade-in + slight translate-y on route change
- Respects
prefers-reduced-motion - Uses SolidJS
<Transition>or CSS animations
- Loading states integrated:
- Dashboard widgets show skeletons while
createResourceis loading - Service pages show skeleton layouts while data fetches
- Buttons show spinner inside during mutation loading
- Forms show disabled state with spinner during submission
- Dashboard widgets show skeletons while
- Accessibility improvements:
- All images have
alttext - All interactive elements have focus rings
- Color contrast meets WCAG AA (4.5:1 for text)
- ARIA labels on icon-only buttons
- Skip-to-content link for keyboard navigation
- Reduced motion support for all animations
- All images have
web/src/components/ui/EmptyState.tsx— Empty state component:- Icon, title, description, and optional action button
- Used when lists have no items (e.g., no watchlist items, no alerts)
steps:
- Create
web/src/components/ui/ErrorBoundary.tsx:- Use SolidJS
ErrorBoundarycomponent as base - Custom fallback UI with ShieldAI logo, error message, retry button
- Capture stack trace for debugging
- Use SolidJS
- Create
web/src/components/ui/Skeleton.tsx:- Use Tailwind
animate-pulseor custom shimmer animation - Each skeleton variant accepts
lines,width,heightprops - Use rounded rectangles that mimic content shape
- Use Tailwind
- Create
web/src/components/ui/PageTransition.tsx:- Wrap route content in transition group
- Apply
opacity-0 translate-y-2→opacity-100 translate-y-0on enter - Duration: 200ms, easing: ease-out
- Integrate skeletons:
- Dashboard: replace
Loading...text withSkeletonCardgrids - Service pages: add skeleton layouts matching final content shape
- Tables: use
SkeletonTablewith 5 rows
- Dashboard: replace
- Integrate loading states in buttons:
- Update
Buttonprimitive to show spinner whenloadingprop is true - Disable button and reduce opacity during loading
- Update
- Add accessibility:
- Audit all pages with axe DevTools
- Fix any contrast issues (adjust colors in theme if needed)
- Add
aria-labelto all icon buttons - Add
aria-live="polite"regions for toast notifications - Ensure all form inputs have associated labels
- Create
EmptyStatecomponent and use it across pages:- DarkWatch: "No watchlist items yet" with "Add first item" button
- VoicePrint: "No voice enrollments" with "Enroll voice" button
- SpamShield: "No custom rules" with "Create rule" button
- etc.
- Test all improvements across light/dark modes.
steps:
- Unit: ErrorBoundary catches thrown errors and renders fallback
- Unit: Skeleton components render with correct dimensions
- Unit: PageTransition applies enter animation
- Accessibility: axe DevTools audit passes with 0 critical violations
- Visual: All loading states look polished and match content layout
- Visual: Empty states display correctly when lists are empty
acceptance_criteria:
- ErrorBoundary catches and displays friendly error UI for all routes
- Skeleton screens match the shape of final content (no layout shift)
- All buttons show loading state during mutations
- Page transitions are smooth and respect reduced motion
- Empty states are informative and provide clear next actions
- axe DevTools audit shows 0 critical or serious violations
- All color combinations meet WCAG AA contrast standards
- Keyboard navigation works for all interactive elements
validation:
- Throw an error in a component and verify ErrorBoundary catches it
- Throttle network to 3G and verify skeletons appear during loading
- Navigate between routes and verify smooth transitions
- Run axe DevTools on each page and fix any issues
- Test keyboard-only navigation (Tab, Enter, Escape) on all pages
- Run
cd web && pnpm testfor polish-related unit tests
notes:
- Reference Lendair's skeleton components:
~/code/Lendair/web/src/components/skeletons/ - The
animate-pulseTailwind class is sufficient for skeletons. For a more polished look, consider a custom shimmer animation with a moving gradient. - Error boundaries should NOT catch errors in event handlers or async code — only render errors. Use try/catch for async operations.
- For monitoring integration, consider adding Sentry in a follow-up task. For now, log errors to console.
- The
EmptyStatecomponent should be reusable across all service pages. Keep it generic but allow customization of icon, title, description, and action. - Test reduced motion by enabling "Reduce motion" in OS settings and verifying all animations are suppressed.