---
phase: 28-vu-meter-optimization
plan: 02
subsystem: ui
tags: [react, performance, vu-meters, requestAnimationFrame, direct-dom]

# Dependency graph
requires:
  - phase: 28-01
    provides: "External vuStore with RAF batching and useSyncExternalStore shim"
provides:
  - "VU data routing from native bridge to external store"
  - "Direct DOM manipulation for VU meter visuals via refs"
  - "Zero React re-renders from 50-70 VU updates/second"
affects: [future-performance-work, high-frequency-updates]

# Tech tracking
tech-stack:
  added: []
  patterns:
    - "Direct DOM manipulation via refs for high-frequency visuals"
    - "Per-component RAF loops for UI polling pattern"
    - "External store integration without Redux/Context"

key-files:
  created: []
  modified:
    - jam-ui/src/hooks/useMixerStore.js
    - jam-ui/src/hooks/useVuHelpers.js
    - jam-ui/src/context/VuContext.js
    - jam-ui/src/components/client/SessionTrackVU.js
    - jam-ui/src/stores/vuStore.js

key-decisions:
  - "Route VU data directly to vuStore in bridge callback, bypassing React state"
  - "Per-component RAF loops over centralized subscription system (simpler for multiple mixer IDs)"
  - "React.memo on SessionTrackVU to prevent parent re-render propagation"
  - "className assignment over classList.add/remove for DOM efficiency"
  - "Remove VU state management from useVuHelpers, keep legacy functions for backward compatibility"

patterns-established:
  - "Direct DOM updates via refs: Store element refs, use RAF loop to poll external store, manipulate DOM directly without triggering React reconciliation"
  - "External store integration: Import store directly in components, bypass Context/Redux for high-frequency data"
  - "Bridge callback optimization: Route native events to external store instead of Redux dispatch"

# Metrics
duration: ~45min (across multiple sessions, including checkpoint verification)
completed: 2026-03-05
---

# Phase 28 Plan 02: Wire Components to External Store Summary

**VU meters now animate via direct DOM updates at 60fps with zero React re-renders from 50-70 updates/sec native bridge data flow**

## Performance

- **Duration:** ~45 minutes (across multiple sessions with checkpoint pause)
- **Started:** 2026-03-05T08:00:00Z (approximate)
- **Completed:** 2026-03-05T09:46:57Z
- **Tasks:** 4 (3 auto + 1 human-verify checkpoint)
- **Files modified:** 5

## Accomplishments

- **Native bridge integration:** useMixerStore now routes VU data directly to vuStore.updateLevel, bypassing Redux entirely
- **Direct DOM rendering:** SessionTrackVU rewritten with RAF loop and ref-based DOM manipulation - no React re-renders for VU visuals
- **Simplified state management:** useVuHelpers and VuContext no longer manage VU state, only provide store reference
- **Zero render overhead:** React DevTools confirms no re-renders from VU updates after verification approval

## Task Commits

Each task was committed atomically:

1. **Task 1: Update useMixerStore to route VU data to external store** - `96d8f97` (refactor)
2. **Task 2: Simplify useVuHelpers and VuContext** - `f821485` (refactor)
3. **Task 3: Rewrite SessionTrackVU with direct DOM updates** - `a020e27` (refactor)
4. **Task 4: Human verification** - APPROVED (VU meters animating correctly after fix)

**Additional fix during verification:**
- **Fix: getLevelSnapshot check pendingUpdates** - `a1d24fe38` (fix)

**Plan metadata:** (to be committed after this summary)

## Files Created/Modified

- `jam-ui/src/hooks/useMixerStore.js` - Routes VU data from native bridge to vuStore.updateLevel
- `jam-ui/src/hooks/useVuHelpers.js` - Removed useState, exposes vuStore methods, kept legacy functions
- `jam-ui/src/context/VuContext.js` - Simplified to provide vuStore reference only
- `jam-ui/src/components/client/SessionTrackVU.js` - Complete rewrite with RAF loop and direct DOM manipulation via refs
- `jam-ui/src/stores/vuStore.js` - Fixed getLevelSnapshot to check pendingUpdates for immediate access

## Decisions Made

**1. Route VU data in bridge callback**
- **Decision:** Call vuStore.updateLevel directly in useMixerStore handleBridgeCallback
- **Rationale:** Intercepts VU data at source, prevents Redux dispatch entirely
- **Impact:** 50-70 updates/sec now go to external buffer instead of triggering React state updates

**2. Per-component RAF loops over subscription**
- **Decision:** Each SessionTrackVU runs own RAF loop polling vuStore.getLevelSnapshot
- **Rationale:** Simpler than subscription system for multiple mixer IDs, RAF auto-pauses when tab backgrounded
- **Impact:** Each VU meter polls at ~60fps (display refresh rate), matches browser capabilities

**3. Direct DOM manipulation pattern**
- **Decision:** Store light element refs, update className directly via RAF loop
- **Rationale:** Bypasses React reconciliation entirely, proven pattern for high-frequency visuals
- **Impact:** Zero React involvement in VU animation - confirmed via DevTools

**4. React.memo on SessionTrackVU**
- **Decision:** Wrap component with memo to prevent parent re-render propagation
- **Rationale:** Even though VU updates don't trigger re-renders, parent changes (mixer config) shouldn't force re-render
- **Impact:** Component only re-renders when mixers prop reference changes

**5. Keep legacy functions in useVuHelpers**
- **Decision:** Preserve createQualifiedId, renderVU, updateVU for backward compatibility
- **Rationale:** Other components may still use these functions, minimize scope of changes
- **Impact:** Clean migration path, no breaking changes to consumers

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 1 - Bug] getLevelSnapshot not checking pendingUpdates**
- **Found during:** Task 4 (Human verification)
- **Issue:** VU meters not animating - getLevelSnapshot returned null for newly updated levels because it only checked flushedLevels, but RAF loop was calling it before next flush cycle
- **Fix:** Modified getLevelSnapshot to check pendingUpdates first, then fall back to flushedLevels - provides immediate access to most recent data
- **Files modified:** jam-ui/src/stores/vuStore.js
- **Verification:** VU meters started animating immediately after fix
- **Committed in:** a1d24fe38 (fix commit after verification checkpoint)

---

**Total deviations:** 1 auto-fixed (1 bug)
**Impact on plan:** Critical fix for correctness - getLevelSnapshot must provide immediate access to buffered updates or RAF loop sees stale data. No scope creep, just fixing incomplete implementation from Plan 01.

## Issues Encountered

**VU meters not animating after initial implementation**
- **Problem:** SessionTrackVU RAF loop called getLevelSnapshot but got null/undefined, no visual updates
- **Root cause:** getLevelSnapshot only checked flushedLevels (updated every 16ms), but RAF loop (60fps = ~16ms) was reading between flush cycles
- **Solution:** Updated getLevelSnapshot to check pendingUpdates buffer first - provides immediate access without waiting for next flush
- **Outcome:** VU meters animated correctly after fix, approved by user verification

## User Setup Required

None - no external service configuration required.

## Next Phase Readiness

**What's ready:**
- VU meter optimization complete (requirements VU-01, VU-02, VU-03 satisfied)
- External store pattern established for other high-frequency updates
- Direct DOM manipulation pattern proven for React 16 performance optimization

**Next steps:**
- Continue with remaining plans in Phase 28 (if any)
- Monitor VU meter performance in production for extended sessions
- Consider applying external store pattern to other high-frequency updates (e.g., audio waveforms, real-time meters)

**No blockers or concerns.**

---
*Phase: 28-vu-meter-optimization*
*Completed: 2026-03-05*
