diff --git a/muxplex/__pycache__/cli.cpython-313.pyc b/muxplex/__pycache__/cli.cpython-313.pyc index dfe2d32..3801e73 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 41b833c..560f84a 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 a60a228..15a5f1d 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 new file mode 100644 index 0000000..5c11913 Binary files /dev/null and b/muxplex/__pycache__/settings.cpython-313.pyc differ diff --git a/muxplex/frontend/app.js b/muxplex/frontend/app.js index 3aa3d62..0bf2082 100644 --- a/muxplex/frontend/app.js +++ b/muxplex/frontend/app.js @@ -448,6 +448,17 @@ function buildSidebarHTML(session, currentSession) { ); } +/** + * Returns sessions with hidden session names removed. + * Consolidates the hidden-session filter used by all render paths. + * @param {object[]} sessions + * @returns {object[]} + */ +function getVisibleSessions(sessions) { + const hidden = (_serverSettings && _serverSettings.hidden_sessions) || []; + return (sessions || []).filter((s) => !hidden.includes(s.name)); +} + /** * Render the session sidebar list. Only renders in fullscreen view. * Shows empty state when no sessions exist. @@ -461,9 +472,7 @@ function renderSidebar(sessions, currentSession) { const list = $('sidebar-list'); if (!list) return; - // Filter hidden sessions - const hiddenSessions = (_serverSettings && _serverSettings.hidden_sessions) || []; - const visible = (sessions || []).filter((s) => !hiddenSessions.includes(s.name)); + const visible = getVisibleSessions(sessions); if (visible.length === 0) { list.innerHTML = ''; @@ -594,9 +603,7 @@ function renderGrid(sessions) { const grid = $('session-grid'); const emptyState = $('empty-state'); - // Filter hidden sessions - const hiddenSessions = (_serverSettings && _serverSettings.hidden_sessions) || []; - const visible = (sessions || []).filter((s) => !hiddenSessions.includes(s.name)); + const visible = getVisibleSessions(sessions); if (visible.length === 0) { if (grid) grid.innerHTML = ''; diff --git a/muxplex/frontend/terminal.js b/muxplex/frontend/terminal.js index 1d33d7f..78d2159 100644 --- a/muxplex/frontend/terminal.js +++ b/muxplex/frontend/terminal.js @@ -150,7 +150,7 @@ function createTerminal() { } } catch (_) { /* use default 14 */ } - const mobile = window.innerWidth < 600; + const mobile = window.innerWidth < 600; // matches MOBILE_THRESHOLD in app.js const fontSize = mobile ? Math.min(storedFontSize, 12) : storedFontSize; _term = new window.Terminal({ 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 99d03c0..e453b00 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 db2d8bb..fed0abb 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_cli.cpython-313-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_cli.cpython-313-pytest-9.0.2.pyc index ab51c7d..100638b 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-312-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_frontend_css.cpython-312-pytest-9.0.2.pyc index 93ad681..49a1338 100644 Binary files a/muxplex/tests/__pycache__/test_frontend_css.cpython-312-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_frontend_css.cpython-312-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 0578297..71fd5ba 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-312-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_frontend_html.cpython-312-pytest-9.0.2.pyc index 0f77138..0331d92 100644 Binary files a/muxplex/tests/__pycache__/test_frontend_html.cpython-312-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_frontend_html.cpython-312-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 c0f7d16..0121eb1 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-312-pytest-9.0.2.pyc b/muxplex/tests/__pycache__/test_frontend_js.cpython-312-pytest-9.0.2.pyc index dac6c38..fca93a1 100644 Binary files a/muxplex/tests/__pycache__/test_frontend_js.cpython-312-pytest-9.0.2.pyc and b/muxplex/tests/__pycache__/test_frontend_js.cpython-312-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 new file mode 100644 index 0000000..0a2d32c Binary files /dev/null and b/muxplex/tests/__pycache__/test_frontend_js.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 4838237..8c0b4f2 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 new file mode 100644 index 0000000..36db5a2 Binary files /dev/null and b/muxplex/tests/__pycache__/test_settings.cpython-313-pytest-9.0.2.pyc differ diff --git a/muxplex/tests/test_frontend_js.py b/muxplex/tests/test_frontend_js.py index 9e8a401..deace2d 100644 --- a/muxplex/tests/test_frontend_js.py +++ b/muxplex/tests/test_frontend_js.py @@ -4,9 +4,11 @@ import pathlib import re JS_PATH = pathlib.Path(__file__).parent.parent / "frontend" / "app.js" +TERMINAL_JS_PATH = pathlib.Path(__file__).parent.parent / "frontend" / "terminal.js" # Read once per module — tests are read-only so sharing is safe. _JS: str = JS_PATH.read_text() +_TERMINAL_JS: str = TERMINAL_JS_PATH.read_text() # ── Palette state variables must be removed ────────────────────────────────── @@ -2050,10 +2052,6 @@ def test_js_show_fab_session_input_uses_factory() -> None: # ─── Task 7: Apply settings effects (task-7-apply-settings-effects) ────────── -TERMINAL_JS_PATH = pathlib.Path(__file__).parent.parent / "frontend" / "terminal.js" -_TERMINAL_JS: str = TERMINAL_JS_PATH.read_text() - - # ── terminal.js: createTerminal reads font size from localStorage ───────────── @@ -2136,11 +2134,31 @@ def test_create_terminal_has_default_font_size_14() -> None: ) +# ── app.js: getVisibleSessions helper filters hidden sessions ───────────────── + + +def test_get_visible_sessions_filters_hidden_sessions() -> None: + """getVisibleSessions() must filter sessions using _serverSettings.hidden_sessions.""" + match = re.search( + r"function getVisibleSessions\s*\(\w+\)\s*\{(.*?)(?=\n(?:function|//|window\.))", + _JS, + re.DOTALL, + ) + assert match, "getVisibleSessions function not found in app.js" + body = match.group(1) + assert "hidden_sessions" in body, ( + "getVisibleSessions must filter sessions using _serverSettings.hidden_sessions" + ) + assert "_serverSettings" in body, ( + "getVisibleSessions must reference _serverSettings to access hidden_sessions" + ) + + # ── app.js: renderGrid filters hidden sessions ──────────────────────────────── def test_render_grid_filters_hidden_sessions() -> None: - """renderGrid() must filter out hidden sessions using _serverSettings.hidden_sessions.""" + """renderGrid() must filter out hidden sessions via getVisibleSessions().""" match = re.search( r"function renderGrid\s*\(\w+\)\s*\{(.*?)(?=\n(?:function|//|window\.))", _JS, @@ -2148,11 +2166,8 @@ def test_render_grid_filters_hidden_sessions() -> None: ) assert match, "renderGrid function not found in app.js" body = match.group(1) - assert "hidden_sessions" in body, ( - "renderGrid must filter sessions using _serverSettings.hidden_sessions" - ) - assert "_serverSettings" in body, ( - "renderGrid must reference _serverSettings to access hidden_sessions" + assert "getVisibleSessions" in body, ( + "renderGrid must call getVisibleSessions() to filter hidden sessions" ) @@ -2171,7 +2186,7 @@ def test_render_grid_creates_visible_array() -> None: def test_render_grid_uses_visible_for_empty_state_check() -> None: - """renderGrid() must use visible array (not sessions) for empty-state check.""" + """renderGrid() must use visible.length (not sessions.length) for empty-state check.""" match = re.search( r"function renderGrid\s*\(\w+\)\s*\{(.*?)(?=\n(?:function|//|window\.))", _JS, @@ -2179,10 +2194,10 @@ def test_render_grid_uses_visible_for_empty_state_check() -> None: ) assert match, "renderGrid function not found in app.js" body = match.group(1) - # After filtering hidden sessions, the empty state check should use visible.length, - # not sessions.length. Check that visible is used before the empty state handling. - assert "visible" in body, ( - "renderGrid must use 'visible' array for empty-state check" + # The empty-state guard must check visible.length, not sessions.length, so hidden + # sessions do not trigger the "no sessions" state. + assert "visible.length" in body, ( + "renderGrid must use 'visible.length' for empty-state check, not sessions.length" ) @@ -2221,7 +2236,7 @@ def test_render_grid_reads_sort_order_from_server_settings() -> None: def test_render_sidebar_filters_hidden_sessions() -> None: - """renderSidebar() must filter out hidden sessions using _serverSettings.hidden_sessions.""" + """renderSidebar() must filter out hidden sessions via getVisibleSessions().""" match = re.search( r"function renderSidebar\s*\(\w+,\s*\w+\)\s*\{(.*?)(?=\nconst SIDEBAR_KEY|function |\n// ─)", _JS, @@ -2229,11 +2244,8 @@ def test_render_sidebar_filters_hidden_sessions() -> None: ) assert match, "renderSidebar function not found in app.js" body = match.group(1) - assert "hidden_sessions" in body, ( - "renderSidebar must filter sessions using _serverSettings.hidden_sessions" - ) - assert "_serverSettings" in body, ( - "renderSidebar must reference _serverSettings to access hidden_sessions" + assert "getVisibleSessions" in body, ( + "renderSidebar must call getVisibleSessions() to filter hidden sessions" )