feat: Live Dashboard Animations — Sparklines, Counters, Pulse & Skeleton#27
feat: Live Dashboard Animations — Sparklines, Counters, Pulse & Skeleton#27
Conversation
Code reviewNo issues found. Checked for bugs and CLAUDE.md compliance. 🤖 Generated with Claude Code - If this code review was useful, please react with 👍. Otherwise, react with 👎. |
Add animations.css with all @Keyframes (skeleton-shimmer, panel-pulse, slide-in-left, fade-out-right, blink), skeleton loading classes, instability bar styles, and reduced-motion/idle-pause overrides. Add IdleDetector utility that pauses CSS animations after 2 minutes of inactivity via body.animations-paused class, following worldmonitor's idle detection pattern. Also pauses on tab visibility change. Part of #22 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
AnimatedCounter: rAF-based number roller with ease-out cubic easing (400ms default). Respects prefers-reduced-motion. Sparkline: D3-based inline SVG sparkline using d3-shape, d3-scale, d3-selection, and d3-transition. Renders 60x20 viewBox with monotone cubic interpolation, animated path transitions (300ms), and semi-transparent area fill. Resolves the D3 phantom dependency. Part of #22 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add opt-in animation methods to PanelBase: triggerPulse() for panel header flash, showSkeleton()/hideSkeleton() for loading placeholders with crossfade, and markDataReceived() to coordinate first-load transitions. All timers tracked and cleaned up via cleanupTimers(). Wire setPanelElement() in PanelManager. Import animations.css and initialize IdleDetector in App.ts. Part of #22 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
MarketTickerPanel: DOM diffing by symbol, D3 sparklines per ticker, AnimatedCounter for prices and change percentages, skeleton loading. InstabilityIndexPanel: DOM diffing by country, horizontal colored bar visualization with CSS gradient, AnimatedCounter for risk scores. NewsFeedPanel: DOM diffing by item.id, staggered slide-in entrance animations, fade-out exit animations, tracked cleanup timers. AIBriefPanel: typing effect that streams sanitized HTML character by character at ~30 chars/sec with blinking cursor. Respects prefers-reduced-motion. All panels use skeleton loading before first data and pulse headers on data arrival. All timers properly tracked and cleaned up. Closes #22 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Update ticker-item layout with gap for sparkline column. Adjust instability-item for bar container flex layout. Update Vite manualChunks to function-based approach that correctly splits D3 subpackages into a separate chunk when used. Part of #22 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add Playwright E2E test suite (9 tests) verifying all visual animation behaviors: skeleton shimmer, header pulse, sparkline transitions, counter rolling, instability bars, news slide-in/out, AI typing effect, idle pause/resume, and prefers-reduced-motion support. Add vitest unit tests for AnimatedCounter (8 tests) and IdleDetector (5 tests). Register market-ticker and instability-index panels in config. Expose App instance in dev mode for test data injection. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Fix AnimatedCounter bug where currentValue was not updated during animation frames, causing visual jumps when setValue() is called mid-animation. Remove noisy mousemove from IdleDetector activity events to reduce unnecessary timer resets. Re-generate config via forge CLI instead of manual edits. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
9c4a519 to
063a944
Compare
alohays
left a comment
There was a problem hiding this comment.
Code Review: PR #27 — Live Dashboard Animations
Overall this is well-structured work. Animations respect prefers-reduced-motion, idle detection follows worldmonitor patterns, and the DOM diffing approach is a solid improvement over wholesale innerHTML replacement. I've pushed fixes for the issues found below.
Fixed in latest commit
1. AnimatedCounter bug (src/core/ui/AnimatedCounter.ts:56)
currentValue was only updated when animation completed. If setValue() was called mid-animation, the new animation started from the stale currentValue instead of the current visual value, causing a backward jump. Fixed by updating currentValue on every frame tick.
2. IdleDetector performance (src/core/ui/IdleDetector.ts:2)
mousemove in ACTIVITY_EVENTS fires hundreds of times per second, each call triggering classList.remove() + clearTimeout() + setTimeout(). Removed it — mousedown, keydown, scroll, touchstart provide sufficient idle detection coverage.
3. monitor-forge.config.json direct edit (AGENTS.md violation)
Config was manually edited to add market-ticker and instability-index panels. Reverted to main's version and re-added panels via forge panel add CLI as required by AGENTS.md.
Notes (no action needed)
- DOMPurify →
textContent: The refactor fromDOMPurify.sanitize()+innerHTMLtotextContentin MarketTickerPanel, InstabilityIndexPanel, and NewsFeedPanel is safe —textContentinherently prevents XSS. vite.config.tsmanualChunks: Function-based approach correctly handles D3 subpackages (d3-shape,d3-scale, etc.) that the old object form missed.import 'd3-transition'in Sparkline.ts: Side-effect import that monkey-patchesSelection.prototype— works correctly but could benefit from a brief explanatory comment.- PR description accuracy: States "no new npm deps" but
@playwright/testwas added as a devDependency.
Branch update
Rebased onto main to pick up commit 5f8687d (3D Globe). No merge conflicts. All 470 tests pass, typecheck clean, config valid.
🤖 Generated with Claude Code
- Remove unused priceHistory Map from MarketTickerPanel (memory waste) - Remove unused refreshInterval field from AIBriefPanel - Add data shape validation in MarketTickerPanel and InstabilityIndexPanel to reject incompatible data from updateAll() broadcasts - Replace ineffective DOMPurify.sanitize() on URLs with protocol whitelist (https/http only) in NewsFeedPanel - Fix skeleton line class trailing space in PanelBase via classList.add() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
Brings the dashboard to life with real-time visual feedback, resolving the D3 phantom dependency and replacing wholesale
innerHTMLreplacement with incremental DOM diffing across all panel types.prefers-reduced-motion: reduceWorldmonitor alignment
Verified against https://github.com/koala73/worldmonitor — adopted idle detection pattern (
body.animations-paused), incremental panel updates, and CSS keyframe approach. Diverges intentionally where issue requirements differ (D3 sparklines vs plain polyline, shimmer skeletons vs opacity states, typing effect).Test plan
tsc --noEmit— typecheck passesvitest run— 454/454 tests pass (30 files), including new AnimatedCounter + IdleDetector unit testsvite build— production build succeeds, D3 chunks correctly when usedprefers-reduced-motion: reducedisables all animationsAll 9 visual tests verified via Playwright E2E (
e2e/visual-animations.spec.ts) running in headless Chromium with programmatic DOM state assertions and screenshot capture at key animation moments.Closes #22
🤖 Generated with Claude Code