# 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 placeholders - `SkeletonCard` — card-shaped skeleton with header, body, footer - `SkeletonAvatar` — circular skeleton for profile images - `SkeletonTable` — table row skeletons - All use `.bg-bg-tertiary` with 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 `` or CSS animations - Loading states integrated: - Dashboard widgets show skeletons while `createResource` is loading - Service pages show skeleton layouts while data fetches - Buttons show spinner inside during mutation loading - Forms show disabled state with spinner during submission - Accessibility improvements: - All images have `alt` text - 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 - `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: 1. Create `web/src/components/ui/ErrorBoundary.tsx`: - Use SolidJS `ErrorBoundary` component as base - Custom fallback UI with ShieldAI logo, error message, retry button - Capture stack trace for debugging 2. Create `web/src/components/ui/Skeleton.tsx`: - Use Tailwind `animate-pulse` or custom shimmer animation - Each skeleton variant accepts `lines`, `width`, `height` props - Use rounded rectangles that mimic content shape 3. Create `web/src/components/ui/PageTransition.tsx`: - Wrap route content in transition group - Apply `opacity-0 translate-y-2` → `opacity-100 translate-y-0` on enter - Duration: 200ms, easing: ease-out 4. Integrate skeletons: - Dashboard: replace `Loading...` text with `SkeletonCard` grids - Service pages: add skeleton layouts matching final content shape - Tables: use `SkeletonTable` with 5 rows 5. Integrate loading states in buttons: - Update `Button` primitive to show spinner when `loading` prop is true - Disable button and reduce opacity during loading 6. Add accessibility: - Audit all pages with axe DevTools - Fix any contrast issues (adjust colors in theme if needed) - Add `aria-label` to all icon buttons - Add `aria-live="polite"` regions for toast notifications - Ensure all form inputs have associated labels 7. Create `EmptyState` component 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. 8. 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 test` for polish-related unit tests notes: - Reference Lendair's skeleton components: `~/code/Lendair/web/src/components/skeletons/` - The `animate-pulse` Tailwind 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 `EmptyState` component 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.