diff --git a/muxplex/__pycache__/auth.cpython-313.pyc b/muxplex/__pycache__/auth.cpython-313.pyc index d16c0e4..2634239 100644 Binary files a/muxplex/__pycache__/auth.cpython-313.pyc and b/muxplex/__pycache__/auth.cpython-313.pyc differ diff --git a/muxplex/__pycache__/bells.cpython-313.pyc b/muxplex/__pycache__/bells.cpython-313.pyc index b45198d..8acbb9d 100644 Binary files a/muxplex/__pycache__/bells.cpython-313.pyc and b/muxplex/__pycache__/bells.cpython-313.pyc differ diff --git a/muxplex/__pycache__/cli.cpython-313.pyc b/muxplex/__pycache__/cli.cpython-313.pyc index 3801e73..47da95b 100644 Binary files a/muxplex/__pycache__/cli.cpython-313.pyc and b/muxplex/__pycache__/cli.cpython-313.pyc differ diff --git a/muxplex/__pycache__/main.cpython-313.pyc b/muxplex/__pycache__/main.cpython-313.pyc index 560f84a..36cab23 100644 Binary files a/muxplex/__pycache__/main.cpython-313.pyc and b/muxplex/__pycache__/main.cpython-313.pyc differ diff --git a/muxplex/__pycache__/sessions.cpython-313.pyc b/muxplex/__pycache__/sessions.cpython-313.pyc index 15a5f1d..3627d6d 100644 Binary files a/muxplex/__pycache__/sessions.cpython-313.pyc and b/muxplex/__pycache__/sessions.cpython-313.pyc differ diff --git a/muxplex/__pycache__/settings.cpython-313.pyc b/muxplex/__pycache__/settings.cpython-313.pyc index 5c11913..492914b 100644 Binary files a/muxplex/__pycache__/settings.cpython-313.pyc and b/muxplex/__pycache__/settings.cpython-313.pyc differ diff --git a/muxplex/__pycache__/state.cpython-313.pyc b/muxplex/__pycache__/state.cpython-313.pyc index 9bc1f11..cbf0b66 100644 Binary files a/muxplex/__pycache__/state.cpython-313.pyc and b/muxplex/__pycache__/state.cpython-313.pyc differ diff --git a/muxplex/__pycache__/ttyd.cpython-313.pyc b/muxplex/__pycache__/ttyd.cpython-313.pyc index aeba78f..eb1eaf1 100644 Binary files a/muxplex/__pycache__/ttyd.cpython-313.pyc and b/muxplex/__pycache__/ttyd.cpython-313.pyc differ diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 838f966..3f58469 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -1792,9 +1792,7 @@ function openSettings() { } // Update document.title from device_name setting - if (ss && ss.device_name) { - document.title = ss.device_name; - } + document.title = (ss && ss.device_name) || 'muxplex'; // Multi-device enabled checkbox (with smart default: checked if remote_instances non-empty) const multiDeviceEnabledEl = $('setting-multi-device-enabled'); @@ -2351,7 +2349,7 @@ function bindStaticEventListeners() { on($('setting-device-name'), 'input', function() { clearTimeout(_deviceNameDebounceTimer); var val = this.value; - if (val) document.title = val; + document.title = val || 'muxplex'; _deviceNameDebounceTimer = setTimeout(function() { patchServerSetting('device_name', val).then(function() { _sources = buildSources(_serverSettings); @@ -2359,7 +2357,7 @@ function bindStaticEventListeners() { }, 500); }); - // Sessions tab — add remote instance button + // Multi-Device tab — add remote instance button on($('add-remote-instance-btn'), 'click', function() { var container = $('setting-remote-instances'); if (container) { @@ -2367,7 +2365,7 @@ function bindStaticEventListeners() { } }); - // Sessions tab — delegated remove handler on remote instances container + // Multi-Device tab — delegated remove handler on remote instances container var remoteInstancesContainer = $('setting-remote-instances'); if (remoteInstancesContainer) { remoteInstancesContainer.addEventListener('click', function(e) { @@ -2519,7 +2517,9 @@ document.addEventListener('DOMContentLoaded', () => { restoreState() .then(() => { startPolling(); - loadServerSettings(); + loadServerSettings().then(function() { + document.title = _serverSettings.device_name || 'muxplex'; + }); startHeartbeat(); bindStaticEventListeners(); }) diff --git a/muxplex/frontend/tests/test_app.mjs b/muxplex/frontend/tests/test_app.mjs index 03b6df1..ce5b160 100644 --- a/muxplex/frontend/tests/test_app.mjs +++ b/muxplex/frontend/tests/test_app.mjs @@ -3564,3 +3564,62 @@ test('CSS style.css has scrollbar-width none for fit mode pre to hide scrollbar' 'style.css must have scrollbar-width: none for hidden scrollbar in fit mode pre' ); }); + +// --- document.title quality fixes --- + +test('device name input handler restores default title when value is cleared', () => { + const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); + // Must use fallback form, not the conditional-only form + assert.ok( + source.includes("document.title = val || 'muxplex'"), + "device name input handler must set document.title = val || 'muxplex' to restore default when cleared" + ); + assert.ok( + !source.includes("if (val) document.title = val"), + "device name input handler must NOT use conditional 'if (val) document.title = val' (skips restore)" + ); +}); + +test('openSettings restores default title unconditionally when device_name is absent', () => { + const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); + assert.ok( + source.includes("document.title = (ss && ss.device_name) || 'muxplex'"), + "openSettings must set document.title = (ss && ss.device_name) || 'muxplex' unconditionally" + ); + assert.ok( + !source.includes("if (ss && ss.device_name) {\n document.title = ss.device_name;\n }"), + "openSettings must NOT use conditional block that skips restore when device_name is absent" + ); +}); + +test('remote instance event listener comments say Multi-Device tab not Sessions tab', () => { + const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); + assert.ok( + !source.includes('// Sessions tab \u2014 add remote instance button'), + "comment for add-remote-instance-btn must say 'Multi-Device tab', not 'Sessions tab'" + ); + assert.ok( + !source.includes('// Sessions tab \u2014 delegated remove handler'), + "comment for delegated remove handler must say 'Multi-Device tab', not 'Sessions tab'" + ); + assert.ok( + source.includes('// Multi-Device tab \u2014 add remote instance button'), + "add-remote-instance-btn comment must say '// Multi-Device tab \u2014 add remote instance button'" + ); + assert.ok( + source.includes('// Multi-Device tab \u2014 delegated remove handler'), + "delegated remove handler comment must say '// Multi-Device tab \u2014 delegated remove handler'" + ); +}); + +test('DOMContentLoaded sets document.title from server settings at page load', () => { + const source = fs.readFileSync(new URL('../app.js', import.meta.url), 'utf8'); + // Find the DOMContentLoaded block + const domIdx = source.indexOf("document.addEventListener('DOMContentLoaded'"); + assert.ok(domIdx !== -1, 'DOMContentLoaded handler must exist'); + const domBlock = source.substring(domIdx, domIdx + 800); + assert.ok( + domBlock.includes("document.title = _serverSettings.device_name || 'muxplex'"), + "DOMContentLoaded must set document.title = _serverSettings.device_name || 'muxplex' after loadServerSettings resolves" + ); +}); diff --git a/muxplex/tests/__pycache__/test_api.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_api.cpython-313-pytest-9.0.2.pyc index e453b00..5e91309 100644 Binary files a/muxplex/tests/__pycache__/test_api.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_api.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_auth.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_auth.cpython-313-pytest-9.0.2.pyc index fed0abb..ed7d12a 100644 Binary files a/muxplex/tests/__pycache__/test_auth.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_auth.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_bells.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_bells.cpython-313-pytest-9.0.2.pyc index 9ec9567..daf18f0 100644 Binary files a/muxplex/tests/__pycache__/test_bells.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_bells.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_cli.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_cli.cpython-313-pytest-9.0.2.pyc index 100638b..c757d31 100644 Binary files a/muxplex/tests/__pycache__/test_cli.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_cli.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_frontend_css.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_frontend_css.cpython-313-pytest-9.0.2.pyc index 8c775b5..8dd2c10 100644 Binary files a/muxplex/tests/__pycache__/test_frontend_css.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_frontend_css.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_frontend_html.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_frontend_html.cpython-313-pytest-9.0.2.pyc index 0121eb1..7dc5302 100644 Binary files a/muxplex/tests/__pycache__/test_frontend_html.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_frontend_html.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_frontend_js.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_frontend_js.cpython-313-pytest-9.0.2.pyc index 0a2d32c..234c775 100644 Binary files a/muxplex/tests/__pycache__/test_frontend_js.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_frontend_js.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_integration.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_integration.cpython-313-pytest-9.0.2.pyc index 23d2c1f..c4ab8d1 100644 Binary files a/muxplex/tests/__pycache__/test_integration.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_integration.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_sessions.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_sessions.cpython-313-pytest-9.0.2.pyc index 8c0b4f2..8f66fc7 100644 Binary files a/muxplex/tests/__pycache__/test_sessions.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_sessions.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_settings.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_settings.cpython-313-pytest-9.0.2.pyc index 36db5a2..7f1374a 100644 Binary files a/muxplex/tests/__pycache__/test_settings.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_settings.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_state.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_state.cpython-313-pytest-9.0.2.pyc index e198dd1..0ee8ebc 100644 Binary files a/muxplex/tests/__pycache__/test_state.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_state.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_ttyd.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_ttyd.cpython-313-pytest-9.0.2.pyc index dfcae7e..e398294 100644 Binary files a/muxplex/tests/__pycache__/test_ttyd.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_ttyd.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/__pycache__/test_ws_proxy.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_ws_proxy.cpython-313-pytest-9.0.2.pyc index 12d7aac..adeed34 100644 Binary files a/muxplex/tests/__pycache__/test_ws_proxy.cpython-313-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_ws_proxy.cpython-313-pytest-9.0.2.pyc differ