diff --git a/app/src/components/BottomSheet.tsx b/app/src/components/BottomSheet.tsx index 891cb59..1f33d2d 100644 --- a/app/src/components/BottomSheet.tsx +++ b/app/src/components/BottomSheet.tsx @@ -43,7 +43,14 @@ export function BottomSheet({ isOpen, onClose, title, children }: BottomSheetPro if (!isOpen) return null; return ( -
+
{ if (e.key === 'Escape') onClose(); }} + role="dialog" + aria-modal="true" + aria-label={title || 'Bottom sheet'} + >
{title &&
{title}
} diff --git a/app/src/components/EffectsPanel.tsx b/app/src/components/EffectsPanel.tsx index 0d342e8..037b556 100644 --- a/app/src/components/EffectsPanel.tsx +++ b/app/src/components/EffectsPanel.tsx @@ -128,8 +128,9 @@ export function EffectsPanel({ Reverb
- + {Math.round(effects.reverb.wet * 100)}%
- + Delay
- + {Math.round(effects.delay.wet * 100)}%
- +
- + Chorus
- + {Math.round(effects.chorus.wet * 100)}%
- + {effects.chorus.frequency.toFixed(1)}Hz
- + Distortion
- + {Math.round(effects.distortion.wet * 100)}%
- + handleExampleClick(ex)} + onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') handleExampleClick(ex); }} + role="button" + tabIndex={0} >
{pattern.map((row, ri) => ( diff --git a/app/src/components/MixerPanel.tsx b/app/src/components/MixerPanel.tsx index d21513f..445e438 100644 --- a/app/src/components/MixerPanel.tsx +++ b/app/src/components/MixerPanel.tsx @@ -149,8 +149,9 @@ const MixerChannel = memo(function MixerChannel({ {/* Per-track swing (if handler provided) */} {onSetSwing && (
- + { - return melodicTracks.filter(track => - anySoloed ? track.soloed : !track.muted - ).length; - }, [melodicTracks, anySoloed]); + const visibleTrackCount = melodicTracks.filter(track => + anySoloed ? track.soloed : !track.muted + ).length; // Build per-step pitch data const stepData = useMemo((): StepPitchData[] => { diff --git a/app/src/components/QROverlay/QRCode.tsx b/app/src/components/QROverlay/QRCode.tsx index 655b78b..b7f6059 100644 --- a/app/src/components/QROverlay/QRCode.tsx +++ b/app/src/components/QROverlay/QRCode.tsx @@ -19,14 +19,18 @@ interface QRCodeProps { className?: string; } +interface QRState { + svgString: string; + error: string | null; +} + export function QRCode({ value, size = 200, errorCorrection = 'M', className = '', }: QRCodeProps) { - const [svgString, setSvgString] = useState(''); - const [error, setError] = useState(null); + const [state, setState] = useState({ svgString: '', error: null }); useEffect(() => { let cancelled = false; @@ -45,12 +49,11 @@ export function QRCode({ }); if (!cancelled) { - setSvgString(svg); - setError(null); + setState({ svgString: svg, error: null }); } } catch (err) { if (!cancelled) { - setError(err instanceof Error ? err.message : 'Failed to generate QR code'); + setState({ svgString: '', error: err instanceof Error ? err.message : 'Failed to generate QR code' }); } } } @@ -62,7 +65,7 @@ export function QRCode({ }; }, [value, size, errorCorrection]); - if (error) { + if (state.error) { return (
); } diff --git a/app/src/components/SessionName.tsx b/app/src/components/SessionName.tsx index 8e37d0a..cdd51dc 100644 --- a/app/src/components/SessionName.tsx +++ b/app/src/components/SessionName.tsx @@ -20,13 +20,6 @@ export function SessionName({ name, sessionId, onRename, disabled = false }: Ses const [isSaving, setIsSaving] = useState(false); const inputRef = useRef(null); - // Sync edit value when name prop changes - useEffect(() => { - if (!isEditing) { - setEditValue(name || ''); - } - }, [name, isEditing]); - // Focus input when entering edit mode useEffect(() => { if (isEditing && inputRef.current) { @@ -42,9 +35,10 @@ export function SessionName({ name, sessionId, onRename, disabled = false }: Ses const handleClick = useCallback(() => { if (!disabled && !isSaving) { + setEditValue(name || ''); setIsEditing(true); } - }, [disabled, isSaving]); + }, [disabled, isSaving, name]); const handleSave = useCallback(async () => { if (isSaving) return; diff --git a/app/src/components/StepSequencer.tsx b/app/src/components/StepSequencer.tsx index 2cfeff9..30bff5a 100644 --- a/app/src/components/StepSequencer.tsx +++ b/app/src/components/StepSequencer.tsx @@ -321,9 +321,7 @@ export function StepSequencer() { }, [dragState.targetTrackId, state.tracks, dispatch, multiplayer]); // Phase 31D: Count muted tracks for button display - const mutedTrackCount = useMemo(() => { - return state.tracks.filter(t => t.muted).length; - }, [state.tracks]); + const mutedTrackCount = state.tracks.filter(t => t.muted).length; // Phase 31F: Selection state and handler const handleSelectStep = useCallback((trackId: string, step: number, mode: 'toggle' | 'extend') => { diff --git a/app/src/components/ToastNotification.tsx b/app/src/components/ToastNotification.tsx index 4634d5b..d4944f6 100644 --- a/app/src/components/ToastNotification.tsx +++ b/app/src/components/ToastNotification.tsx @@ -88,6 +88,9 @@ function ToastItem({ toast, onDismiss }: { toast: Toast; onDismiss: (id: string)
{ if (e.key === 'Enter' || e.key === ' ') handleUrlTap(); }} + role="button" + tabIndex={0} >
{toast.message} diff --git a/app/src/components/TrackRow.tsx b/app/src/components/TrackRow.tsx index 2827f0e..a5a57d3 100644 --- a/app/src/components/TrackRow.tsx +++ b/app/src/components/TrackRow.tsx @@ -771,6 +771,7 @@ export const TrackRow = React.memo(function TrackRow({
setIsMenuOpen(!isMenuOpen)} + onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') setIsMenuOpen(!isMenuOpen); }} role="button" tabIndex={0} > diff --git a/app/src/components/Transport.tsx b/app/src/components/Transport.tsx index ba9787e..61886ae 100644 --- a/app/src/components/Transport.tsx +++ b/app/src/components/Transport.tsx @@ -304,8 +304,9 @@ export function Transport({ />
- + {Math.round(effects.reverb.wet * 100)}%
- + Delay
- + {Math.round(effects.delay.wet * 100)}%
- +
- + Chorus
- + {Math.round(effects.chorus.wet * 100)}%
- + {effects.chorus.frequency.toFixed(1)}Hz
- + Distortion
- + {Math.round(effects.distortion.wet * 100)}%
- + BPM {tempo} @@ -142,6 +148,12 @@ export function TransportBar({ onMouseUp={handleDragEnd} onMouseLeave={handleDragEnd} title="Drag up/down to adjust swing" + role="slider" + aria-label="Swing" + aria-valuemin={0} + aria-valuemax={100} + aria-valuenow={swing} + tabIndex={0} > Swing {swing}% diff --git a/app/src/components/Waveform.tsx b/app/src/components/Waveform.tsx index a2a2ecc..f16e9b4 100644 --- a/app/src/components/Waveform.tsx +++ b/app/src/components/Waveform.tsx @@ -15,7 +15,9 @@ interface WaveformProps { height?: number; } -export function Waveform({ buffer, slicePoints = [], onSlicePointsChange, onPlaySlice, height = 80 }: WaveformProps) { +const EMPTY_SLICE_POINTS: number[] = []; + +export function Waveform({ buffer, slicePoints = EMPTY_SLICE_POINTS, onSlicePointsChange, onPlaySlice, height = 80 }: WaveformProps) { const canvasRef = useRef(null); const containerRef = useRef(null); const [hoveredSlice, setHoveredSlice] = useState(null); @@ -189,6 +191,8 @@ export function Waveform({ buffer, slicePoints = [], onSlicePointsChange, onPlay onMouseDown={handleMouseDown} onMouseUp={handleMouseUp} onMouseLeave={handleMouseLeave} + role="img" + aria-label="Audio waveform" >