Create Laser Harp Component
When to Use This Skill
- •User requests to create a new component
- •Implementing components from architecture: App, ControlPanel, Stage, VideoLayer, CanvasLayer, EffectLayer
- •Need to follow established patterns for consistency
- •Adding new UI components to the laser harp
Component Architecture
App (root)
├── ControlPanel (UI controls)
└── Stage (display area)
├── VideoLayer (camera feed - hidden, but needed as source)
├── EffectLayer (silhouette rendering)
└── CanvasLayer (zone boundaries + glow effects)
Templates Available
- •
template-functional.tsx- Standard functional component with TypeScript - •
template-canvas.tsx- Canvas-based component with useRef and resize handling
Component Guidelines
For ControlPanel Component
Location: src/components/ControlPanel.tsx
Props:
interface ControlPanelProps {
config: AppConfig;
onConfigChange: (config: AppConfig) => void;
onStart: () => void;
onStop: () => void;
isRunning: boolean;
}
Responsibilities:
- •Display Start/Stop button
- •Show numStrings selector (range: 3-12)
- •Show scale selector (major, minor, pentatonic)
- •Show baseNote selector (C4-B4)
- •Trigger Tone.start() on Start button click
- •Clean, minimal UI that doesn't obstruct camera view
Key Implementation Points:
- •Start button must call
await Tone.start()before onStart() - •Disable controls while running
- •Use semantic HTML (buttons, selects, labels)
- •Provide visual feedback for running state
For Stage Component
Location: src/components/Stage.tsx
Props:
interface StageProps {
width: number;
height: number;
children: React.ReactNode;
}
Responsibilities:
- •Container for VideoLayer, EffectLayer, and CanvasLayer
- •Relative positioning
- •Maintains aspect ratio
- •Handles responsive sizing
Key Implementation Points:
- •Use
position: relativefor container - •Children should be absolutely positioned
- •Set explicit width/height
- •Center align in page
For VideoLayer Component
Location: src/components/VideoLayer.tsx
Props:
interface VideoLayerProps {
videoRef: RefObject<HTMLVideoElement>;
}
Responsibilities:
- •Provide camera video stream as source for EffectLayer
- •Hidden from view (opacity: 0) - silhouette only
- •Mirror video horizontally for natural interaction
- •Full size within Stage
Key Implementation Points:
- •Use
<video>element with ref - •Apply
transform: scaleX(-1)for mirroring - •Set autoPlay, playsInline attributes
- •Muted (no audio from camera)
- •opacity: 0 - video element is needed for camera access but not displayed
- •Position absolute, full width/height
For EffectLayer Component
Location: src/components/EffectLayer.tsx
Props:
interface EffectLayerProps {
videoRef: RefObject<HTMLVideoElement>;
width: number;
height: number;
}
Responsibilities:
- •Render silhouette effect from video feed
- •Apply brightness/contrast processing for cyberpunk aesthetic
- •Real-time frame processing
Key Implementation Points:
- •Use canvas for pixel manipulation
- •Draw video frame to canvas
- •Apply image processing (brightness, contrast)
- •Create silhouette effect
- •Position absolute, layered above VideoLayer
For CanvasLayer Component
Location: src/components/CanvasLayer.tsx
Props:
interface CanvasLayerProps {
canvasRef: RefObject<HTMLCanvasElement>;
width: number;
height: number;
strings: LaserString[];
hands: TrackedHand[];
activeZones: number[];
}
Responsibilities:
- •Render zone boundaries
- •Draw glow effects for active zones
- •Draw hand tracking visualization
- •Transparent background
- •Handle window resize
Key Implementation Points:
- •Use
<canvas>element with ref - •Set explicit width/height from props
- •Transparent background
- •Position absolute on top of EffectLayer
- •Pointer events: none (don't block interaction)
- •Draw zone boundary lines
- •Apply glow effect for active zones
Implementation Steps
- •
Choose the appropriate template
- •Use
template-functional.tsxfor ControlPanel, Stage, VideoLayer - •Use
template-canvas.tsxfor CanvasLayer, EffectLayer
- •Use
- •
Check if component already exists
bash# Check for existing file ls src/components/[ComponentName].tsx
- •
Copy template and customize
- •Replace
[ComponentName]with actual name - •Define proper TypeScript interfaces for props
- •Import required types from
src/types/index.ts
- •Replace
- •
Implement component logic
- •Add state management (useState)
- •Add effects (useEffect)
- •Add event handlers
- •Follow React best practices
- •
Add styling
- •Use className for CSS styling
- •Keep styles minimal for MVP
- •Ensure accessibility
- •
Verify TypeScript types
- •All props have interfaces
- •No implicit any types
- •Proper return type annotations
React Best Practices
Hooks Usage
- •Use
useStatefor local state - •Use
useEffectfor side effects - •Use
useReffor DOM refs and mutable values - •Use
useCallbackfor stable callbacks (if needed)
Performance
- •Avoid premature optimization
- •Use React.memo only if needed
- •Keep components focused and small
Error Handling
- •Add error boundaries for critical components
- •Handle null/undefined refs safely
- •Provide fallback UI for errors
Type Definitions
All types should be imported from src/types/index.ts:
import type { AppConfig, Point2D, TrackedHand, LaserString } from '../types';
See .claude/docs/data-structures.md for complete type definitions.
Examples
See examples/ directory for complete reference implementations:
- •
ControlPanel.example.tsx- Full ControlPanel with all features - •
Stage.example.tsx- Stage component with layout
Common Patterns
Controlled Components
const ControlPanel: React.FC<ControlPanelProps> = ({ config, onConfigChange }) => {
const handleNumStringsChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
const numStrings = parseInt(e.target.value, 10);
onConfigChange({ ...config, numStrings });
};
return (
<select value={config.numStrings} onChange={handleNumStringsChange}>
{/* options */}
</select>
);
};
Canvas Ref Management
const CanvasLayer: React.FC<CanvasLayerProps> = ({ canvasRef, width, height }) => {
useEffect(() => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Drawing logic - zone boundaries and glow effects
}, [canvasRef, width, height]);
return <canvas ref={canvasRef} width={width} height={height} />;
};
Video Ref Management (Hidden)
const VideoLayer: React.FC<VideoLayerProps> = ({ videoRef }) => {
return (
<video
ref={videoRef}
autoPlay
playsInline
muted
style={{
transform: 'scaleX(-1)',
opacity: 0 // Hidden - silhouette only
}}
/>
);
};
Effect Layer Pattern
const EffectLayer: React.FC<EffectLayerProps> = ({ videoRef, width, height }) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
useEffect(() => {
const canvas = canvasRef.current;
const video = videoRef.current;
if (!canvas || !video) return;
const ctx = canvas.getContext('2d');
if (!ctx) return;
// Frame processing loop for silhouette effect
}, [videoRef, width, height]);
return <canvas ref={canvasRef} width={width} height={height} />;
};
Styling Guidelines
ControlPanel
- •Fixed or floating position
- •Non-intrusive (top or bottom)
- •Clear visual hierarchy
- •Sufficient contrast
Stage
- •Centered in viewport
- •Responsive sizing
- •Clear boundaries
VideoLayer
- •Full size within Stage
- •Hidden (opacity: 0)
- •Mirrored for natural interaction
EffectLayer
- •Full size within Stage
- •Renders silhouette effect
- •Layered above VideoLayer
CanvasLayer
- •Transparent background
- •Same size as other layers
- •Layered on top
- •Zone boundaries and glow effects
Troubleshooting
Issue: Component not rendering
- •Check if component is exported
- •Verify import path
- •Check for TypeScript errors
Issue: Props not updating
- •Ensure proper state lifting
- •Check if parent is passing updated props
- •Verify useEffect dependencies
Issue: Ref is null
- •Check if ref is passed correctly
- •Ensure DOM element is mounted
- •Add null checks before using ref
Issue: Silhouette not showing
- •Verify VideoLayer is receiving camera stream
- •Check EffectLayer canvas dimensions
- •Ensure video element is playing
Success Criteria
After creating a component, verify:
- • TypeScript compiles without errors
- • Component renders without console errors
- • Props are properly typed
- • Responsive to prop changes
- • Follows project conventions
- • Accessible (semantic HTML, ARIA if needed)