From 4fa9ed17fd4a6495069598081c3b52963986ce3f Mon Sep 17 00:00:00 2001 From: Nuwan Date: Fri, 16 Jan 2026 16:39:20 +0530 Subject: [PATCH] fix(05-03): prevent tracks from disappearing when metronome clicked MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fixed critical bug where all tracks (Audio Inputs and Session Mix) disappeared when user clicked "Open" → "Metronome". Root cause: useEffect in JKSessionScreen.js depended on entire sessionHelper object, which got new reference whenever currentSession updated (including when metronome_open flag changed). This triggered unnecessary onSessionChange() calls that refetched all mixers, causing race condition where myTracks became empty. Changes: 1. **JKSessionScreen.js (line 540)**: Changed useEffect dependency from `sessionHelper` to `sessionHelper.id()` (primitive value that only changes when session ID changes, not on metadata updates) 2. **useMixerHelper.js (lines 63, 388-396, 453-454)**: Added previousMyTracksRef to store last known good myTracks. Returns previous value instead of empty array when allMixers is temporarily empty during state transitions. 3. **useMixerStore.js (lines 198-201, 213-220, 231)**: Added verification logging to confirm onSessionChange is not called unnecessarily. Can be removed after testing confirms fix works. Expected behavior after fix: - ✅ Tracks DO NOT disappear when metronome clicked - ✅ onSessionChange only called on initial session join, not on metadata updates - ✅ Audio Inputs and Session Mix remain visible throughout metronome operations - ✅ Backing tracks and jam tracks still work correctly (regression test) NOTE: This fix does NOT implement metronome UI display. Metronome audio will play but no mixer controls will appear in session screen. That can be added later as separate task. Co-Authored-By: Claude Sonnet 4.5 --- .../src/components/client/JKSessionScreen.js | 2 +- jam-ui/src/hooks/useMixerHelper.js | 25 ++++++++++++++++--- jam-ui/src/hooks/useMixerStore.js | 12 +++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/jam-ui/src/components/client/JKSessionScreen.js b/jam-ui/src/components/client/JKSessionScreen.js index ae865cd38..724a84e0b 100644 --- a/jam-ui/src/components/client/JKSessionScreen.js +++ b/jam-ui/src/components/client/JKSessionScreen.js @@ -537,7 +537,7 @@ const JKSessionScreen = () => { useEffect(() => { if (!isConnected || !hasJoined) return; onSessionChange(sessionHelper); - }, [isConnected, hasJoined, sessionHelper]); + }, [isConnected, hasJoined, sessionHelper.id()]); const ensureAppropriateProfile = async (musicianAccess) => { return new Promise(async function (resolve, reject) { diff --git a/jam-ui/src/hooks/useMixerHelper.js b/jam-ui/src/hooks/useMixerHelper.js index 59d97e239..fc9e3c0bc 100644 --- a/jam-ui/src/hooks/useMixerHelper.js +++ b/jam-ui/src/hooks/useMixerHelper.js @@ -60,6 +60,7 @@ import { getAvatarUrl, getInstrumentIcon45, getInstrumentIcon24 } from '../helpe const useMixerHelper = () => { const dispatch = useDispatch(); const allMixersRef = useRef({}); + const previousMyTracksRef = useRef([]); // Redux selectors - replace all useState calls const chatMixer = useSelector(selectChatMixer); @@ -376,14 +377,30 @@ const useMixerHelper = () => { // Compute myTracks - memoized to prevent infinite re-renders const myTracks = useMemo(() => { - //console.debug("useMixerHelper: computing myTracks", { isConnected, currentSession, jamClient, allMixers }); + console.debug("useMixerHelper: computing myTracks", { + isConnected, + currentSession, + jamClient, + allMixers, + allMixersCount: allMixers ? Object.keys(allMixers).length : 0 + }); - if (!isConnected || !inSession || !jamClient || !allMixers) return []; + if (!isConnected || !inSession || !jamClient || !allMixers) { + return previousMyTracksRef.current; // Return previous value, not [] + } + + // Safety check: if allMixers is empty during state transition, return previous value + if (typeof allMixers === 'object' && Object.keys(allMixers).length === 0) { + console.warn("useMixerHelper: allMixers is empty, returning previous myTracks"); + return previousMyTracksRef.current; // Return previous value, not [] + } //const participant = currentSession.participants?.[jamClient.clientId]; const participant = getParticipant(server.clientId); - if (!participant) return []; + if (!participant) { + return previousMyTracksRef.current; // Return previous value, not [] + } const tracks = []; const connStatsClientId = participant.client_role === 'child' && participant.parent_client_id @@ -433,6 +450,8 @@ const useMixerHelper = () => { }); } + // Store for next time + previousMyTracksRef.current = tracks; return tracks; }, [currentSession, isConnected, jamClient, allMixers, mixMode, findMixerForTrack, getParticipant, server.clientId]); diff --git a/jam-ui/src/hooks/useMixerStore.js b/jam-ui/src/hooks/useMixerStore.js index c47cb7288..305c8f318 100644 --- a/jam-ui/src/hooks/useMixerStore.js +++ b/jam-ui/src/hooks/useMixerStore.js @@ -195,7 +195,12 @@ export default function useMixerStore() { }, []); const onSessionChange = useCallback(async (newSession) => { + console.log("[MixerStore] onSessionChange START:", { + sessionId: newSession.id(), + timestamp: Date.now() + }); console.debug("MixerStore: onSessionChange", newSession); + if (!newSession) return; if (!inSession) { @@ -205,9 +210,15 @@ export default function useMixerStore() { setSession(newSession); if (jamClient) { + console.log("[MixerStore] Fetching mixer control state..."); const newMasterMixers = await jamClient.SessionGetAllControlState(true); const newPersonalMixers = await jamClient.SessionGetAllControlState(false); + console.log("[MixerStore] Fetched mixers:", { + masterCount: newMasterMixers.length, + personalCount: newPersonalMixers.length + }); + // Initialize the mixerHelper with the constructor parameters console.debug("MixerStore: onSessionChange: initializing mixerHelper", { newSession, newMasterMixers, newPersonalMixers, metro, noAudioUsers, clientsWithAudioOverride }); @@ -217,6 +228,7 @@ export default function useMixerStore() { // console.debug("MixerStore: onSessionChange: ", m); // setMixers(m); } + console.log("[MixerStore] onSessionChange END"); }, [jamClient, mixerHelper, metro, noAudioUsers, clientsWithAudioOverride, inSession, sessionEnded]); // // Mixer actions