diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js
index 48893f8..7a812b7 100644
--- a/muxplex/frontend/app.js
+++ b/muxplex/frontend/app.js
@@ -133,7 +133,6 @@ let _previewTimer = null;
var _previewSessionName = null; // track by NAME, not DOM element
// ─── Settings state ───────────────────────────────────────────────────────────
-let _sources = [];
let _settingsOpen = false;
let _serverSettings = null;
let _gridViewMode = 'flat';
@@ -467,7 +466,7 @@ function buildTileHTML(session, index, mobile) {
// Device badge — right-aligned in header, separate from name span
// Shown when multiple sources configured AND session has a device name
let badgeHtml = '';
- if (_sources.length > 1 && session.deviceName && ds.showDeviceBadges !== false) {
+ if (_serverSettings && _serverSettings.multi_device_enabled && session.deviceName && ds.showDeviceBadges !== false) {
badgeHtml = `${escapeHtml(session.deviceName)}`;
}
@@ -522,9 +521,9 @@ function buildSidebarHTML(session, currentSession) {
// Edge bar only (left border amber, no glow): applied when actIndicator is 'dot' or 'both'
if (isBell && (actIndicator === 'dot' || actIndicator === 'both')) classes += ' sidebar-item--edge-bell';
- // Device badge — shown in meta line when multiple sources configured
+ // Device badge — shown in meta line when multi_device_enabled
let badgeHtml = '';
- if (_sources.length > 1 && session.deviceName && ds.showDeviceBadges !== false) {
+ if (_serverSettings && _serverSettings.multi_device_enabled && session.deviceName && ds.showDeviceBadges !== false) {
badgeHtml = `${escapeHtml(session.deviceName)}`;
}
@@ -606,6 +605,22 @@ function buildOfflineTileHTML(source) {
);
}
+/**
+ * Build the HTML string for a generic status tile (auth_failed or unreachable).
+ * @param {string} deviceName
+ * @param {string} statusText
+ * @param {string} statusClass
+ * @returns {string}
+ */
+function buildStatusTileHTML(deviceName, statusText, statusClass) {
+ return (
+ '' +
+ '' + escapeHtml(deviceName || '') + '' +
+ '' + escapeHtml(statusText || '') + '' +
+ ''
+ );
+}
+
/**
* Open a login popup window for a remote muxplex instance.
* Strips trailing slashes from remoteUrl before appending /login.
@@ -656,8 +671,8 @@ function renderSidebar(sessions, currentSession) {
let html = '';
- if (_sources.length > 1) {
- // Group sessions by deviceName when multiple sources configured
+ if (_serverSettings && _serverSettings.multi_device_enabled) {
+ // Group sessions by deviceName when multi_device_enabled
const groups = new Map();
for (const session of visible) {
const deviceName = session.deviceName || 'Unknown';
@@ -871,14 +886,12 @@ function renderGrid(sessions) {
}
if (visible.length === 0) {
- // Build status tiles for non-authenticated sources even when no sessions exist
+ // Build status tiles for auth_failed/unreachable sessions even when no regular sessions exist
var statusTilesHtml = '';
- if (typeof _sources !== 'undefined' && _sources) {
- _sources.forEach(function(source) {
- if (source.status === 'auth_required') statusTilesHtml += buildAuthTileHTML(source);
- else if (source.status === 'unreachable') statusTilesHtml += buildOfflineTileHTML(source);
- });
- }
+ (sessions || []).forEach(function(session) {
+ if (session.status === 'auth_failed') statusTilesHtml += buildStatusTileHTML(session.name, 'Auth required', 'auth');
+ else if (session.status === 'unreachable') statusTilesHtml += buildStatusTileHTML(session.name, 'Offline', 'offline');
+ });
if (grid) grid.innerHTML = statusTilesHtml;
// Only show empty-state when there are truly no tiles at all
if (emptyState) {
@@ -916,17 +929,12 @@ function renderGrid(sessions) {
html = ordered.map(function(session, index) { return buildTileHTML(session, index, mobile); }).join('');
}
- // Append status tiles for auth_required and unreachable sources
+ // Append status tiles for auth_failed and unreachable sessions
var statusTilesHtml = '';
- if (typeof _sources !== 'undefined' && _sources) {
- _sources.forEach(function(source) {
- if (source.status === 'auth_required') {
- statusTilesHtml += buildAuthTileHTML(source);
- } else if (source.status === 'unreachable') {
- statusTilesHtml += buildOfflineTileHTML(source);
- }
- });
- }
+ (sessions || []).forEach(function(session) {
+ if (session.status === 'auth_failed') statusTilesHtml += buildStatusTileHTML(session.name, 'Auth required', 'auth');
+ else if (session.status === 'unreachable') statusTilesHtml += buildStatusTileHTML(session.name, 'Offline', 'offline');
+ });
if (grid) grid.innerHTML = html + statusTilesHtml;
// Render filter bar
@@ -1387,7 +1395,6 @@ async function loadServerSettings() {
console.warn('[loadServerSettings] failed:', err);
if (!_serverSettings) _serverSettings = {};
}
- _sources = buildSources(_serverSettings);
return _serverSettings;
}
@@ -1409,42 +1416,6 @@ async function patchServerSetting(key, value) {
}
}
-/**
- * Build the list of session sources from server settings.
- * Local source is always first with url: ''.
- * Remote instances come from settings.remote_instances array.
- * Trailing slashes are stripped from remote URLs.
- * Default device name is 'This device' when device_name is empty.
- * @param {object} settings - server settings object
- * @returns {object[]} array of source objects with {url, name, type, status, backoffMs}
- */
-function buildSources(settings) {
- var localName = (settings && settings.device_name) || 'This device';
- var sources = [
- { url: '', name: localName, type: 'local', status: 'authenticated', backoffMs: 2000 },
- ];
- // Only add remote sources when multi-device is enabled.
- // Smart default: treat as enabled if remote_instances is non-empty (backward compat).
- var remotes = (settings && settings.remote_instances) || [];
- var multiDeviceEnabled = (settings && settings.multi_device_enabled) ||
- (remotes.length > 0);
- if (multiDeviceEnabled) {
- for (var i = 0; i < remotes.length; i++) {
- var r = remotes[i];
- if (r && r.url) {
- sources.push({
- url: r.url.replace(/\/+$/, ''),
- name: r.name || r.url,
- type: 'remote',
- status: 'authenticated',
- backoffMs: 2000,
- });
- }
- }
- }
- return sources;
-}
-
/**
* Build a single remote instance row element with URL input, name input, and remove button.
* @param {string} url - remote instance URL
@@ -1478,7 +1449,6 @@ function _buildRemoteInstanceRow(url, name) {
/**
* Read remote instance rows from the DOM and save to server settings.
- * Rebuilds _sources after saving so polling updates immediately.
*/
function _saveRemoteInstances() {
var container = $('setting-remote-instances');
@@ -1493,26 +1463,7 @@ function _saveRemoteInstances() {
instances.push({ url: url, name: name });
}
});
- patchServerSetting('remote_instances', instances).then(function() {
- _sources = buildSources(_serverSettings);
- // Prune federation tokens for URLs no longer in the remote instances list
- try {
- var activeOrigins = instances.map(function(r) {
- try { return new URL(r.url).origin; } catch (_) { return null; }
- }).filter(Boolean);
- var ftokens = JSON.parse(localStorage.getItem('muxplex.federation_tokens') || '{}');
- var pruned = false;
- Object.keys(ftokens).forEach(function(origin) {
- if (!activeOrigins.includes(origin)) {
- delete ftokens[origin];
- pruned = true;
- }
- });
- if (pruned) {
- localStorage.setItem('muxplex.federation_tokens', JSON.stringify(ftokens));
- }
- } catch (_) { /* blocked — ok */ }
- });
+ patchServerSetting('remote_instances', instances);
}
// ─── Multi-Device helper ──────────────────────────────────────────────────────────
@@ -2379,9 +2330,7 @@ function bindStaticEventListeners() {
on($('setting-multi-device-enabled'), 'change', function() {
var enabled = this.checked;
_updateMultiDeviceFieldsState(enabled);
- patchServerSetting('multi_device_enabled', enabled).then(function() {
- _sources = buildSources(_serverSettings);
- });
+ patchServerSetting('multi_device_enabled', enabled);
});
// Multi-Device tab — device name with 500ms debounce; updates document.title immediately
@@ -2391,9 +2340,7 @@ function bindStaticEventListeners() {
var val = this.value;
document.title = val || 'muxplex';
_deviceNameDebounceTimer = setTimeout(function() {
- patchServerSetting('device_name', val).then(function() {
- _sources = buildSources(_serverSettings);
- });
+ patchServerSetting('device_name', val);
}, 500);
});
@@ -2461,36 +2408,6 @@ function bindStaticEventListeners() {
// ─── Test-only helpers ────────────────────────────────────────────────────────
-// ─── Multi-source parallel polling ─────────────────────────────────────────
-
-/**
- * Tag each session in the array with deviceName, sourceUrl, and sessionKey.
- * Returns new session objects; does NOT mutate originals.
- * sessionKey format: sourceUrl + '::' + name
- * @param {object[]} sessions
- * @param {string} deviceName
- * @param {string} sourceUrl
- * @returns {object[]}
- */
-function tagSessions(sessions, deviceName, sourceUrl) {
- return (sessions || []).map((s) => Object.assign({}, s, {
- deviceName,
- sourceUrl,
- sessionKey: sourceUrl + '::' + (s.name || ''),
- }));
-}
-
-/**
- * Merge sessions from multiple sources into a single flat array.
- * Each result is an object with {source, sessions}.
- * Tags each source's sessions with deviceName/sourceUrl/sessionKey.
- * @param {Array<{source: {name: string, url: string}, sessions: object[]}>} results
- * @returns {object[]}
- */
-function mergeSources(results) {
- return (results || []).reduce((all, r) => all.concat(tagSessions(r.sessions, r.source.name, r.source.url)), []);
-}
-
/** Test-only: set _currentSessions directly. */
function _setCurrentSessions(sessions) {
_currentSessions = sessions;
@@ -2501,11 +2418,6 @@ function _setViewMode(mode) {
_viewMode = mode;
}
-/** Test-only: set _sources directly. */
-function _setSources(sources) {
- _sources = sources;
-}
-
/** Test-only: set _serverSettings directly. */
function _setServerSettings(settings) {
_serverSettings = settings;
@@ -2521,11 +2433,6 @@ function _setGridViewMode(mode) {
_gridViewMode = mode;
}
-/** Test-only: get _sources. */
-function _getSources() {
- return _sources;
-}
-
/** Test-only: set _activeFilterDevice directly. */
function _setActiveFilterDevice(device) {
_activeFilterDevice = device;
@@ -2630,7 +2537,6 @@ if (typeof module !== 'undefined' && module.exports) {
// Server settings
loadServerSettings,
patchServerSetting,
- buildSources,
// Fetch wrapper
api,
// Header + button with inline name input
@@ -2639,14 +2545,12 @@ if (typeof module !== 'undefined' && module.exports) {
createNewSession,
// Kill session
killSession,
- // Multi-source parallel polling
- tagSessions,
- mergeSources,
// Filter bar
renderFilterBar,
// Federation tiles
buildAuthTileHTML,
buildOfflineTileHTML,
+ buildStatusTileHTML,
openLoginPopup,
formatLastSeen,
// Federation auth token relay
@@ -2657,11 +2561,9 @@ if (typeof module !== 'undefined' && module.exports) {
// Test-only helpers
_setCurrentSessions,
_setViewMode,
- _setSources,
_setServerSettings,
_getGridViewMode,
_setGridViewMode,
- _getSources,
_setActiveFilterDevice,
};
}
diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs
index 54dabd1..3bc39d1 100644
--- a/muxplex/frontend/tests/test_app.mjs
+++ b/muxplex/frontend/tests/test_app.mjs
@@ -616,7 +616,7 @@ test('renderGrid hides empty-state and populates grid when sessions exist', () =
globalThis.document.querySelectorAll = origQSA;
});
-test('renderGrid includes auth tile HTML when a source is auth_required', () => {
+test('renderGrid includes auth tile HTML when a session has auth_failed status', () => {
const mockGrid = { innerHTML: '' };
const mockEmpty = { style: {}, classList: { add: () => {}, remove: () => {} } };
const origGetById = globalThis.document.getElementById;
@@ -628,12 +628,10 @@ test('renderGrid includes auth tile HTML when a source is auth_required', () =>
};
globalThis.document.querySelectorAll = () => [];
- app._setSources([
- { url: 'http://local:8088', name: 'Local', status: 'authenticated' },
- { url: 'http://workstation:8088', name: 'Workstation', status: 'auth_required' },
- ]);
-
- const sessions = [{ name: 'my-session', snapshot: 'hello' }];
+ const sessions = [
+ { name: 'my-session', snapshot: 'hello' },
+ { name: 'Workstation', status: 'auth_failed' },
+ ];
app.renderGrid(sessions);
assert.ok(mockGrid.innerHTML.includes('source-tile--auth'), 'grid should include auth tile class');
@@ -641,10 +639,9 @@ test('renderGrid includes auth tile HTML when a source is auth_required', () =>
globalThis.document.getElementById = origGetById;
globalThis.document.querySelectorAll = origQSA;
- app._setSources([]);
});
-test('renderGrid includes offline tile HTML when a source is unreachable', () => {
+test('renderGrid includes offline tile HTML when a session has unreachable status', () => {
const mockGrid = { innerHTML: '' };
const mockEmpty = { style: {}, classList: { add: () => {}, remove: () => {} } };
const origGetById = globalThis.document.getElementById;
@@ -656,12 +653,10 @@ test('renderGrid includes offline tile HTML when a source is unreachable', () =>
};
globalThis.document.querySelectorAll = () => [];
- app._setSources([
- { url: 'http://local:8088', name: 'Local', status: 'authenticated' },
- { url: 'http://devserver:8088', name: 'Dev Server', status: 'unreachable', lastSeenAt: Date.now() - 300000 },
- ]);
-
- const sessions = [{ name: 'my-session', snapshot: 'hello' }];
+ const sessions = [
+ { name: 'my-session', snapshot: 'hello' },
+ { name: 'Dev Server', status: 'unreachable' },
+ ];
app.renderGrid(sessions);
assert.ok(mockGrid.innerHTML.includes('source-tile--offline'), 'grid should include offline tile class');
@@ -669,10 +664,9 @@ test('renderGrid includes offline tile HTML when a source is unreachable', () =>
globalThis.document.getElementById = origGetById;
globalThis.document.querySelectorAll = origQSA;
- app._setSources([]);
});
-test('renderGrid shows auth tile and hides empty-state when no sessions but source is auth_required', () => {
+test('renderGrid shows auth tile and hides empty-state when session has auth_failed status', () => {
const addedClasses = [];
const removedClasses = [];
const mockGrid = { innerHTML: '' };
@@ -684,22 +678,17 @@ test('renderGrid shows auth tile and hides empty-state when no sessions but sour
return null;
};
- app._setSources([
- { url: 'http://workstation:8088', name: 'Workstation', status: 'auth_required' },
- ]);
+ // Pass status entry as a session — early-return path (no regular sessions)
+ app.renderGrid([{ name: 'Workstation', status: 'auth_failed' }]);
- // No sessions — early-return path
- app.renderGrid([]);
-
- assert.ok(mockGrid.innerHTML.includes('source-tile--auth'), 'grid should show auth tile even when sessions list is empty');
+ assert.ok(mockGrid.innerHTML.includes('source-tile--auth'), 'grid should show auth tile even when no regular sessions');
assert.ok(addedClasses.includes('hidden'), 'empty-state should be hidden when status tiles are present');
assert.ok(!removedClasses.includes('hidden'), 'empty-state hidden class should NOT be removed when status tiles are present');
globalThis.document.getElementById = origGetById;
- app._setSources([]);
});
-test('renderGrid shows offline tile and hides empty-state when no sessions but source is unreachable', () => {
+test('renderGrid shows offline tile and hides empty-state when session has unreachable status', () => {
const addedClasses = [];
const mockGrid = { innerHTML: '' };
const mockEmpty = { style: {}, classList: { add: (c) => addedClasses.push(c), remove: () => {} } };
@@ -710,18 +699,13 @@ test('renderGrid shows offline tile and hides empty-state when no sessions but s
return null;
};
- app._setSources([
- { url: 'http://devbox:8088', name: 'Dev Box', status: 'unreachable', lastSeenAt: Date.now() - 120000 },
- ]);
+ // Pass status entry as a session — early-return path
+ app.renderGrid([{ name: 'Dev Box', status: 'unreachable' }]);
- // No sessions — early-return path
- app.renderGrid([]);
-
- assert.ok(mockGrid.innerHTML.includes('source-tile--offline'), 'grid should show offline tile even when sessions list is empty');
+ assert.ok(mockGrid.innerHTML.includes('source-tile--offline'), 'grid should show offline tile even when no regular sessions');
assert.ok(addedClasses.includes('hidden'), 'empty-state should be hidden when status tiles are present');
globalThis.document.getElementById = origGetById;
- app._setSources([]);
});
test('_previewClickHandler looks up remoteId from _currentSessions before calling openSession', () => {
@@ -2377,10 +2361,6 @@ test('createNewSession polls for session before auto-opening (not immediate setT
// --- federation state helpers (task-3) ---
-test('_setSources sets internal _sources array', () => {
- assert.doesNotThrow(() => app._setSources([{ url: '', name: 'test' }]));
-});
-
test('_setServerSettings sets internal _serverSettings', () => {
assert.doesNotThrow(() => app._setServerSettings({ sort_order: 'recent' }));
});
@@ -2390,122 +2370,6 @@ test('_getGridViewMode returns current _gridViewMode value', () => {
assert.strictEqual(app._getGridViewMode(), 'flat', '_gridViewMode should default to flat');
});
-test('_getSources returns current _sources array', () => {
- app._setSources([{ url: 'http://test', name: 'Test' }]);
- const sources = app._getSources();
- assert.ok(Array.isArray(sources), '_getSources should return an array');
- assert.strictEqual(sources[0].url, 'http://test', '_getSources should return value set by _setSources');
-});
-
-// --- buildSources ---
-
-test('buildSources returns only local source when no remote_instances', () => {
- const sources = app.buildSources({ device_name: 'Laptop' });
- assert.strictEqual(sources.length, 1, 'should return exactly one source (local)');
- assert.strictEqual(sources[0].url, '', 'local source url should be empty string');
- assert.strictEqual(sources[0].name, 'Laptop', 'local source name should be device_name');
- assert.strictEqual(sources[0].type, 'local', 'local source type should be local');
- assert.strictEqual(sources[0].status, 'authenticated', 'local source status should be authenticated');
-});
-
-test('buildSources returns local + remote sources from remote_instances', () => {
- const sources = app.buildSources({
- device_name: 'Laptop',
- remote_instances: [
- { url: 'https://server1.example.com', name: 'Server 1' },
- { url: 'https://server2.example.com', name: 'Server 2' },
- ],
- });
- assert.strictEqual(sources.length, 3, 'should return 3 sources: 1 local + 2 remote');
- assert.strictEqual(sources[0].type, 'local', 'first source should be local');
- assert.strictEqual(sources[1].type, 'remote', 'second source should be remote');
- assert.strictEqual(sources[1].url, 'https://server1.example.com', 'remote source url should match');
- assert.strictEqual(sources[1].name, 'Server 1', 'remote source name should match');
- assert.strictEqual(sources[2].type, 'remote', 'third source should be remote');
- assert.strictEqual(sources[2].url, 'https://server2.example.com', 'remote source url should match');
-});
-
-test('buildSources uses hostname fallback when device_name is empty', () => {
- const sources = app.buildSources({});
- assert.strictEqual(sources.length, 1, 'should return one source');
- assert.strictEqual(sources[0].name, 'This device', 'local source name should fall back to This device');
-});
-
-test('buildSources strips trailing slash from remote URLs', () => {
- const sources = app.buildSources({
- device_name: 'Laptop',
- remote_instances: [
- { url: 'https://server1.example.com/', name: 'Server 1' },
- { url: 'https://server2.example.com///', name: 'Server 2' },
- ],
- });
- assert.strictEqual(sources[1].url, 'https://server1.example.com', 'trailing slash should be stripped from remote URL');
- assert.strictEqual(sources[2].url, 'https://server2.example.com', 'multiple trailing slashes should be stripped from remote URL');
-});
-
-// --- tagSessions ---
-
-test('tagSessions adds deviceName and sourceUrl to each session', () => {
- const sessions = [{ name: 'work' }, { name: 'play' }];
- const result = app.tagSessions(sessions, 'Laptop', 'https://host.example.com');
- assert.strictEqual(result[0].deviceName, 'Laptop', 'first session should have deviceName set');
- assert.strictEqual(result[0].sourceUrl, 'https://host.example.com', 'first session should have sourceUrl set');
- assert.strictEqual(result[1].deviceName, 'Laptop', 'second session should have deviceName set');
- assert.strictEqual(result[1].sourceUrl, 'https://host.example.com', 'second session should have sourceUrl set');
-});
-
-test('tagSessions adds sessionKey formatted as sourceUrl::name', () => {
- const sessions = [{ name: 'my-session' }];
- const result = app.tagSessions(sessions, 'Laptop', 'https://host.example.com');
- assert.strictEqual(result[0].sessionKey, 'https://host.example.com::my-session', 'sessionKey should be sourceUrl::name');
-});
-
-test('tagSessions handles empty sessions input (returns empty array)', () => {
- const result = app.tagSessions([], 'Laptop', 'https://host.example.com');
- assert.deepStrictEqual(result, [], 'tagSessions should return empty array for empty input');
-});
-
-test('tagSessions does not mutate the original session objects', () => {
- const original = { name: 'work' };
- const sessions = [original];
- const result = app.tagSessions(sessions, 'Laptop', 'https://host.example.com');
- assert.ok(!('deviceName' in original), 'original session should not be mutated');
- assert.ok(!('sourceUrl' in original), 'original session should not have sourceUrl added');
- assert.ok(!('sessionKey' in original), 'original session should not have sessionKey added');
- assert.notStrictEqual(result[0], original, 'returned session should be a new object, not the original');
-});
-
-// --- mergeSources ---
-
-test('mergeSources combines sessions from multiple sources with correct sessionKeys', () => {
- const results = [
- {
- source: { name: 'Laptop', url: '' },
- sessions: [{ name: 'local-session' }],
- },
- {
- source: { name: 'Server', url: 'https://server.example.com' },
- sessions: [{ name: 'remote-session' }],
- },
- ];
- const merged = app.mergeSources(results);
- assert.strictEqual(merged.length, 2, 'merged array should have 2 sessions total');
- const local = merged.find((s) => s.name === 'local-session');
- const remote = merged.find((s) => s.name === 'remote-session');
- assert.ok(local, 'local-session should be present');
- assert.ok(remote, 'remote-session should be present');
- assert.strictEqual(local.sessionKey, '::local-session', 'local session key should be ::local-session');
- assert.strictEqual(remote.sessionKey, 'https://server.example.com::remote-session', 'remote session key should include url');
- assert.strictEqual(remote.deviceName, 'Server', 'remote session should have correct deviceName');
-});
-
-test('mergeSources returns empty array for empty input', () => {
- const result = app.mergeSources([]);
- assert.deepStrictEqual(result, [], 'mergeSources should return empty array for empty input');
-});
-
-
-
// --- getVisibleSessions (task-7) ---
test('getVisibleSessions exported and filters hidden sessions', () => {
@@ -2561,37 +2425,29 @@ test('getVisibleSessions hides local sessions by name but not remote sessions wi
// --- buildSidebarHTML device badge (task-10) ---
-test('buildSidebarHTML shows device-badge when multiple sources configured', () => {
- app._setSources([
- { url: '', name: 'This device', type: 'local', status: 'authenticated', backoffMs: 2000 },
- { url: 'https://remote.example.com', name: 'Remote', type: 'remote', status: 'authenticated', backoffMs: 2000 },
- ]);
+test('buildSidebarHTML shows device-badge when multi_device_enabled', () => {
+ app._setServerSettings({ multi_device_enabled: true });
const session = { name: 'work', deviceName: 'Laptop', sourceUrl: '', sessionKey: '::work', snapshot: '', bell: { unseen_count: 0 } };
const html = app.buildSidebarHTML(session, null);
- assert.ok(html.includes('device-badge'), 'should show device-badge when _sources.length > 1 and session has deviceName');
+ assert.ok(html.includes('device-badge'), 'should show device-badge when multi_device_enabled and session has deviceName');
assert.ok(html.includes('Laptop'), 'device-badge should contain the deviceName');
- app._setSources([]);
+ app._setServerSettings(null);
});
-test('buildSidebarHTML omits device-badge when only one source configured', () => {
- app._setSources([
- { url: '', name: 'This device', type: 'local', status: 'authenticated', backoffMs: 2000 },
- ]);
+test('buildSidebarHTML omits device-badge when multi_device_enabled is false', () => {
+ app._setServerSettings({ multi_device_enabled: false });
const session = { name: 'work', deviceName: 'Laptop', sourceUrl: '', sessionKey: '::work', snapshot: '', bell: { unseen_count: 0 } };
const html = app.buildSidebarHTML(session, null);
- assert.ok(!html.includes('device-badge'), 'should NOT show device-badge when _sources.length is 1');
- app._setSources([]);
+ assert.ok(!html.includes('device-badge'), 'should NOT show device-badge when multi_device_enabled is false');
+ app._setServerSettings(null);
});
test('buildSidebarHTML omits device-badge when session has no deviceName', () => {
- app._setSources([
- { url: '', name: 'This device', type: 'local', status: 'authenticated', backoffMs: 2000 },
- { url: 'https://remote.example.com', name: 'Remote', type: 'remote', status: 'authenticated', backoffMs: 2000 },
- ]);
+ app._setServerSettings({ multi_device_enabled: true });
const session = { name: 'work', sourceUrl: '', sessionKey: '::work', snapshot: '', bell: { unseen_count: 0 } };
const html = app.buildSidebarHTML(session, null);
assert.ok(!html.includes('device-badge'), 'should NOT show device-badge when session has no deviceName');
- app._setSources([]);
+ app._setServerSettings(null);
});
test('buildSidebarHTML includes data-remote-id attribute on article element', () => {
@@ -2614,39 +2470,31 @@ test('buildSidebarHTML data-remote-id is empty string when session has no remote
});
test('buildSidebarHTML escapes HTML in deviceName within device-badge', () => {
- app._setSources([
- { url: '', name: 'This device', type: 'local', status: 'authenticated', backoffMs: 2000 },
- { url: 'https://remote.example.com', name: 'Remote', type: 'remote', status: 'authenticated', backoffMs: 2000 },
- ]);
+ app._setServerSettings({ multi_device_enabled: true });
const session = { name: 'work', deviceName: '', sourceUrl: '', sessionKey: '::work', snapshot: '', bell: { unseen_count: 0 } };
const html = app.buildSidebarHTML(session, null);
assert.ok(!html.includes('', sourceUrl: '', sessionKey: '::work', snapshot: '' };
const html = app.buildTileHTML(session, 0, false);
assert.ok(!html.includes('