From 821c595abe38c33a3e8935578b97156a6c87712e Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Sat, 2 May 2026 06:55:39 -0700 Subject: [PATCH] fix: resolve session switching failure when same-named sessions exist on different devices MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The sidebar click guard, active highlight, mobile bottom sheet, and kill-session auto-close all compared sessions by name only, ignoring remoteId. This caused same-named sessions on different devices to be treated as identical — clicking one while viewing the other was silently discarded as a no-op. Now all 6 comparison sites check both name AND remoteId, using the _viewingRemoteId that was already being tracked but never used in guards. Bumps version to 0.4.4. --- muxplex/frontend/app.js | 30 ++++++++++++++++-------------- pyproject.toml | 2 +- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 13669f9..262e09a 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -354,7 +354,7 @@ async function pollSessions() { _pollFailCount = 0; setConnectionStatus('ok'); renderGrid(sessions); - renderSidebar(sessions, _viewingSession); + renderSidebar(sessions, _viewingSession, _viewingRemoteId); handleBellTransitions(prev, sessions); updateSessionPill(sessions); updateFaviconBadge(); @@ -554,12 +554,13 @@ function buildTileHTML(session, index, mobile) { * Build the HTML string for a single session sidebar card. * @param {object} session * @param {string} currentSession - name of the currently active session + * @param {string} currentRemoteId - remoteId of the currently active session * @returns {string} */ -function buildSidebarHTML(session, currentSession) { +function buildSidebarHTML(session, currentSession, currentRemoteId) { const name = session.name || ''; const escapedName = escapeHtml(name); - const isActive = name === currentSession; + const isActive = name === currentSession && (session.remoteId ?? '') === (currentRemoteId ?? ''); var ds = getDisplaySettings(); var actIndicator = ds.activityIndicator !== undefined ? ds.activityIndicator : 'both'; @@ -697,8 +698,9 @@ function _resolveActiveView(activeView, views) { * Binds click handlers on each sidebar-item to switch sessions. * @param {object[]} sessions * @param {string|null} currentSession - name of the currently active session + * @param {string} [currentRemoteId] - remoteId of the currently active session */ -function renderSidebar(sessions, currentSession) { +function renderSidebar(sessions, currentSession, currentRemoteId) { if (_viewMode !== 'fullscreen') return; const list = $('sidebar-list'); @@ -724,11 +726,11 @@ function renderSidebar(sessions, currentSession) { for (const [deviceName, deviceSessions] of groups) { html += ``; - html += deviceSessions.map((session) => buildSidebarHTML(session, currentSession)).join(''); + html += deviceSessions.map((session) => buildSidebarHTML(session, currentSession, currentRemoteId)).join(''); } } else { // Single source: flat list with no device headers - html = visible.map((session) => buildSidebarHTML(session, currentSession)).join(''); + html = visible.map((session) => buildSidebarHTML(session, currentSession, currentRemoteId)).join(''); } list.innerHTML = html; @@ -740,7 +742,7 @@ function renderSidebar(sessions, currentSession) { const remoteId = item.dataset.remoteId || ''; on(item, 'click', (e) => { if (e.target.closest && e.target.closest('.tile-options-btn')) return; - if (name !== currentSession) openSession(name, { remoteId }); + if (name !== currentSession || remoteId !== (currentRemoteId ?? '')) openSession(name, { remoteId }); }); }); } @@ -1470,7 +1472,7 @@ function switchView(viewName) { _activeView = viewName; closeViewDropdown(); renderGrid(_currentSessions || []); - renderSidebar(_currentSessions || [], _viewingSession); + renderSidebar(_currentSessions || [], _viewingSession, _viewingRemoteId); renderViewDropdown(); // Update sidebar view label to match the active view var sidebarLabel = $('sidebar-view-label'); @@ -2330,7 +2332,7 @@ function _executeKill(name, remoteId) { .then(function() { closeFlyoutMenu(); showToast('Session \'' + name + '\' killed'); - if (_viewingSession === name) { + if (_viewingSession === name && (_viewingRemoteId ?? '') === (remoteId || '')) { closeSession(); } pollSessions(); @@ -2903,7 +2905,7 @@ async function openSession(name, opts = {}) { // Pre-render sidebar with current sessions before first poll tick initSidebar(); - renderSidebar(_currentSessions, name); + renderSidebar(_currentSessions, name, _viewingRemoteId); // Update expanded header const nameEl = $('expanded-session-name'); @@ -2941,7 +2943,7 @@ async function openSession(name, opts = {}) { } // Re-render sidebar after DOM is visible and dimensions are correct initSidebar(); - renderSidebar(_currentSessions, name); + renderSidebar(_currentSessions, name, _viewingRemoteId); resolve(); }, opts.skipAnimation ? 0 : 260); // If setTimeout is stubbed (e.g. in test env), resolve immediately so we don't hang @@ -3643,7 +3645,7 @@ function renderSheetList() { list.innerHTML = sorted.map(function(s) { var hasBell = s.bell && s.bell.unseen_count > 0 && (s.bell.seen_at === null || s.bell.last_fired_at > s.bell.seen_at); - var isActive = s.name === _viewingSession; + var isActive = s.name === _viewingSession && (s.remoteId ?? '') === (_viewingRemoteId ?? ''); var escapedName = escapeHtml(s.name || ''); var remoteIdAttr = s.remoteId != null ? ' data-remote-id="' + escapeHtml(s.remoteId) + '"' : ''; return '