Visual Effect is an interactive visualization tool for the Effect library that demonstrates how Effect operations execute over time. Built with Next.js 15 and React 19, it provides animated visual representations of Effect constructors and combinators with synchronized sound effects, making it easier to understand their behavior.
In this house, we use bun. All package management and script execution should use bun commands, not npm or node.
The VisualEffect class is the heart of the visualization system. It wraps Effect operations and tracks their execution state for visualization purposes.
// Creating a visual effect
const myEffect = visualEffect("taskName", Effect.succeed(42));Key features:
- State tracking: idle → running → completed/failed/interrupted/death
- Observable hooks: React components subscribe via
useVisualEffectState,useVisualEffectNotification, oruseVisualEffectSubscription - Effect caching: Prevents re-execution of already completed effects
- Timer support: Captures start/end timestamps when
showTimeris enabled - Notification helpers: Effects can publish contextual messages through
notify(...) - Sound triggers: Automatically plays sounds on state transitions
The EffectNode component renders individual effects as animated circles with:
- Different colors for different states (idle, running, completed, failed)
- Pulsing animations during execution
- Result display using the renderer system
- Automatic width expansion when results overflow the default size
- Overlay feedback for errors and notifications
Results are displayed using a flexible renderer pattern:
class MyResult implements RenderableResult {
constructor(public value: any) {}
render() {
return <div>{this.value}</div>;
}
}Built-in renderers:
NumberResult- Simple number displayStringResult- Simple string displayBooleanResult- True/false text badgeTemperatureResult- Temperature with a trailing ° symbolObjectResult- JSON stringified objectsArrayResult- Animated array summary (length indicator)EmojiResult- Emoji-based results with enhanced visual appeal
Each example follows a consistent pattern:
export function EffectExampleName() {
// 1. Create individual effects with memoization
const effect1 = useMemo(() => visualEffect("name", effect), []);
// 2. Create composed effect if needed
const resultEffect = useMemo(() => {
const composed = Effect.all([effect1.effect, effect2.effect]);
return new VisualEffect("result", composed, [effect1, effect2]);
}, [effect1, effect2]);
// 3. Define code snippet and highlight mappings
const codeSnippet = `...`;
const effectHighlightMap = { ... };
// 4. Return EffectExample component
return <EffectExample ... />;
}All examples use realistic, non-deterministic delays to simulate real-world conditions:
export function getWeather(location?: string) {
return Effect.gen(function* () {
const delay = getDelay(500, 900); // Random 500-900ms
yield* Effect.sleep(delay);
return new TemperatureResult(...);
});
}- Layout built with Tailwind utility classes and Motion; flex containers wrap naturally on small screens
- Sidebar navigation collapses on narrow viewports while the main content remains accessible
- Typography and spacing scale using relative units for readability across devices
- Each
VisualEffectmanages its own state - React components subscribe via
useVisualEffectState,useVisualEffectNotification, oruseVisualEffectSubscription - Lightweight hooks (
useOptionKey,useStateTransition,useVisualScope) handle UI-specific state - No global state management for effect execution
- Effects persist across component re-renders
- Uses Motion (Framer Motion successor) for smooth transitions
- Spring animations for natural movement with configurable physics
- Different animations for different state transitions
- Hardware-accelerated transforms
- Dedicated sequences for running jitter, failure shakes, and death glitches
The application includes a synthesized sound system using Tone.js:
- Distinct cues: Success, running, failure, interruption, reset, death, ref updates, finalizers, and notifications all receive unique tones
- Shared processing: A centralized
taskSoundsmodule initializes synths, routing, and reverb once and gates playback behind a mute flag - User controls: The header exposes an ON/OFF toggle that updates the mute state and plays a confirmation chime when sound is enabled
- Integration:
VisualEffect.setState()and companion helpers trigger the appropriate cues during state transitions
app/ # Next.js App Router
├── layout.tsx # Root layout with metadata
├── page.tsx # Home page
├── [exampleId]/
│ └── page.tsx # Individual example pages
└── ClientAppContent.tsx # Client-side app content
src/
├── animations.ts # Shared animation tokens
├── AppContent.tsx # Main app component
├── components/
│ ├── CodeBlock.tsx # Syntax-highlighted code
│ ├── HeaderView.tsx # Example headers + controls
│ ├── ScheduleTimeline.tsx # Scheduling visualizer
│ ├── Timer.tsx # Elapsed time labels
│ ├── display/ # Display components
│ │ ├── EffectExample.tsx # Main example wrapper
│ │ └── RefDisplay.tsx # Ref visualizations
│ ├── effect/ # Effect visualization primitives
│ │ ├── EffectNode.tsx
│ │ ├── EffectOverlay.tsx
│ │ ├── taskUtils.ts
│ │ └── useEffectMotion.ts
│ ├── feedback/ # User feedback
│ │ ├── DeathBubble.tsx
│ │ ├── FailureBubble.tsx
│ │ └── NotificationBubble.tsx
│ ├── layout/ # Layout components
│ │ ├── NavigationSidebar.tsx
│ │ └── PageHeader.tsx
│ ├── renderers/ # Result rendering system
│ │ ├── ArrayResult.tsx
│ │ ├── BasicRenderers.tsx
│ │ ├── EmojiResult.tsx
│ │ └── TemperatureResult.tsx
│ ├── scope/
│ │ ├── FinalizerCard.tsx
│ │ └── ScopeStack.tsx
│ └── ui/
│ ├── QuickOpen.tsx
│ ├── SegmentedControl.tsx
│ └── VolumeToggle.tsx
├── constants/
│ ├── colors.ts
│ └── dimensions.ts
├── examples/ # Effect examples
│ ├── helpers.ts # Shared utilities
│ ├── effect-*.tsx # Effect examples
│ └── ref-*.tsx # Ref examples
├── hooks/ # Custom hooks
│ ├── useOptionKey.ts # Option key detection
│ ├── useStateTransition.ts # Effect transition tracking
│ └── useVisualScope.ts # Scope management
├── lib/ # Library code
│ ├── example-types.ts # Type definitions
│ └── examples-manifest.ts # Example registry
├── shared/ # Shared utilities
│ ├── appItems.ts
│ └── idUtils.ts
├── sounds/
│ └── TaskSounds.ts # Synthesized sound system
├── theme.ts # Theme tokens
├── VisualEffect.ts # Core effect visualization
├── VisualRef.ts # Ref visualization
├── VisualScope.ts # Scope visualization
└── VisualEffect.test.ts # Unit tests
Each task manages its own state internally, avoiding complexity and making examples self-contained.
The visualization follows Effect's execution model closely - tasks only run when their effect is executed.
All examples use jittered delays to demonstrate non-deterministic behavior, especially important for race conditions.
The entire UI adapts to mobile screens without compromising the desktop experience.
Strict TypeScript configuration catches errors at compile time, including:
isolatedModulesfor Next.js compatibilitynoUncheckedIndexedAccessfor array safetyexactOptionalPropertyTypesfor precise optional handling- Effect-specific TypeScript plugin for enhanced type checking
Sounds are designed to enhance understanding without being intrusive:
- Short, focused cues map directly to running, completion, failure, interruption, and reset events
- A centralized sound module keeps the palette cohesive and manages initialization/muting
- Automatic sound on state transitions with respectful default levels
- User-friendly mute control without in-app volume sliders (defers to system volume)
- Create a new file in
src/examples/ - Use the
getWeatherhelper for consistent behavior - Follow the example pattern (memoized effects, code snippet, highlight map)
- Add to the examples manifest in
src/lib/examples-manifest.ts - Generate OG images with
bun run generate-og-images
- Implement the
RenderableResultinterface insrc/components/renderers/ - Add a
render()method returning JSX - Export from the renderers index file
- Use in your effect:
Effect.map(value => new MyRenderer(value))
Look in EffectNode.tsx and animations.ts for animation configurations:
- Spring settings including
defaultSpringfor MotionConfig - Color transitions in state change logic
- Timing constants in individual components
- All animation tokens centralized in
animations.ts
- Always memoize effects - Prevents recreation on every render
- Use built-in helpers -
getWeather()for consistency - Keep effects pure - Side effects only for visualization
- Test on mobile - Ensure responsive behavior works
- Follow the pattern - Consistency makes the codebase maintainable
- Use proper accessibility - Provide ARIA labels, focus states, and keyboard-friendly controls
- Optimize bundle size - Lazy load examples and use code splitting
Example Descriptions:
- Use imperative mood (e.g., "Create", "Run", "Compose")
- No ending punctuation (periods, exclamation marks)
- Start with action verbs for consistency
- Keep descriptions concise but informative
Good Examples:
- "Run multiple effects concurrently and compose their results"
- "Interrupt a running effect after a specified duration"
- "Accumulate validation errors instead of failing fast"
Avoid:
- Present tense ("Creates", "Runs", "Composes")
- Ending punctuation ("Create a new task.")
- Passive voice ("A task is created")
UI Text:
- Use proper articles (a, an, the) in prose
- Maintain consistent tone throughout the application
- Keep instructions clear and action-oriented
- Use sentence case for buttons and labels
Error Messages:
- Start with the action or context
- Be specific about what went wrong
- Provide actionable next steps when possible
Code Comments:
- Use present tense for describing what code does
- Keep comments concise and focused on the "why"
- Avoid obvious comments that just restate the code
Migrated from Vite to Next.js 15 for:
- Better SEO with server-side rendering
- Individual pages for each example
- Automatic code splitting and optimization
- Built-in image optimization
Organized components by domain:
display/- Main display logiceffect/- Effect visualization specificsfeedback/- User feedback componentslayout/- Layout and navigationrenderers/- Result rendering systemscope/- Scope and finalizer visualizationui/- Reusable UI components- Top-level helpers (
CodeBlock,HeaderView,Timer,ScheduleTimeline) live alongside these folders
Expanded beyond basic effects to include:
- Ref visualization with
VisualRefclass - Scope visualization with finalizer tracking
- Quick-open modal and link-copy affordances for faster exploration
- Lazy loading for better performance
- Biome for linting and formatting (replaced ESLint/Prettier)
- TypeScript strict mode with Effect language service
- Automated OG image generation for social sharing
- Comprehensive example manifest system