fix(05-03): prevent tracks from disappearing when metronome clicked

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 <noreply@anthropic.com>
This commit is contained in:
Nuwan 2026-01-16 16:39:20 +05:30
parent d264241c78
commit 4fa9ed17fd
3 changed files with 35 additions and 4 deletions

View File

@ -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) {

View File

@ -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]);

View File

@ -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