From a3f1e2efd84abb71b9d0f28f1d23048c7eb04dd4 Mon Sep 17 00:00:00 2001 From: Brian Krabach Date: Fri, 27 Mar 2026 16:35:43 -0700 Subject: [PATCH] feat: add sidebar markup, toggle button, and view-body wrapper to expanded view - Add #sidebar-toggle-btn to expanded-header (after #back-btn, before #expanded-session-name) with aria-label='Toggle session list' and hamburger icon ☰ - Wrap #terminal-container in div.view-body - Add #session-sidebar before #terminal-container inside .view-body, containing: - .sidebar-header with .sidebar-title 'Sessions' and #sidebar-collapse-btn (chevron ‹) - #sidebar-list div - Keep #reconnect-overlay outside .view-body as sibling inside #view-expanded Tests added: - test_html_sidebar_toggle_button: verifies placement, aria-label, and order in header - test_html_view_body_wrapper: verifies .view-body contains #session-sidebar and #terminal-container - test_html_reconnect_overlay_outside_view_body: verifies #reconnect-overlay is outside .view-body - test_html_session_sidebar_structure: verifies full sidebar DOM structure Co-authored-by: Amplifier --- coordinator/tests/test_frontend_html.py | 84 +++++++++++++++++++++++++ frontend/index.html | 26 +++++--- 2 files changed, 102 insertions(+), 8 deletions(-) diff --git a/coordinator/tests/test_frontend_html.py b/coordinator/tests/test_frontend_html.py index a2e1a5d..656d4bb 100644 --- a/coordinator/tests/test_frontend_html.py +++ b/coordinator/tests/test_frontend_html.py @@ -133,6 +133,90 @@ def test_html_style_css() -> None: ) +def test_html_sidebar_toggle_button() -> None: + """#sidebar-toggle-btn must exist in expanded-header with correct aria-label and hamburger icon.""" + soup = _SOUP + btn = soup.find(id="sidebar-toggle-btn") + assert btn is not None, "Missing #sidebar-toggle-btn" + assert btn.get("aria-label") == "Toggle session list", ( + f"#sidebar-toggle-btn aria-label must be 'Toggle session list', got: {btn.get('aria-label')!r}" + ) + # Must be inside expanded-header + header = soup.find("header", class_="expanded-header") + assert header is not None, "Missing header.expanded-header" + assert header.find(id="sidebar-toggle-btn") is not None, ( + "#sidebar-toggle-btn must be inside header.expanded-header" + ) + # Must be after #back-btn and before #expanded-session-name + header_children_ids = [el.get("id") for el in header.children if hasattr(el, "get")] + header_children_ids = [i for i in header_children_ids if i] + assert "back-btn" in header_children_ids, "#back-btn must be in expanded-header" + assert "sidebar-toggle-btn" in header_children_ids, "#sidebar-toggle-btn must be in expanded-header" + assert "expanded-session-name" in header_children_ids, "#expanded-session-name must be in expanded-header" + back_idx = header_children_ids.index("back-btn") + toggle_idx = header_children_ids.index("sidebar-toggle-btn") + name_idx = header_children_ids.index("expanded-session-name") + assert back_idx < toggle_idx < name_idx, ( + f"Order must be back-btn < sidebar-toggle-btn < expanded-session-name, got indices {back_idx}, {toggle_idx}, {name_idx}" + ) + + +def test_html_view_body_wrapper() -> None: + """.view-body div must exist inside #view-expanded wrapping #session-sidebar and #terminal-container.""" + soup = _SOUP + view_expanded = soup.find(id="view-expanded") + assert view_expanded is not None, "Missing #view-expanded" + view_body = view_expanded.find("div", class_="view-body") + assert view_body is not None, "Missing div.view-body inside #view-expanded" + # #terminal-container must be inside .view-body + assert view_body.find(id="terminal-container") is not None, ( + "#terminal-container must be inside div.view-body" + ) + # #session-sidebar must be inside .view-body + assert view_body.find(id="session-sidebar") is not None, ( + "#session-sidebar must be inside div.view-body" + ) + + +def test_html_reconnect_overlay_outside_view_body() -> None: + """#reconnect-overlay must be a direct child of #view-expanded, NOT inside .view-body.""" + soup = _SOUP + view_expanded = soup.find(id="view-expanded") + assert view_expanded is not None, "Missing #view-expanded" + view_body = view_expanded.find("div", class_="view-body") + assert view_body is not None, "Missing div.view-body" + # reconnect-overlay must NOT be inside view-body + assert view_body.find(id="reconnect-overlay") is None, ( + "#reconnect-overlay must NOT be inside div.view-body" + ) + # reconnect-overlay must be inside view-expanded (as sibling of view-body) + assert view_expanded.find(id="reconnect-overlay") is not None, ( + "#reconnect-overlay must be inside #view-expanded" + ) + + +def test_html_session_sidebar_structure() -> None: + """#session-sidebar must contain .sidebar-header (with .sidebar-title and #sidebar-collapse-btn) and #sidebar-list.""" + soup = _SOUP + sidebar = soup.find(id="session-sidebar") + assert sidebar is not None, "Missing #session-sidebar" + # .sidebar-header + sidebar_header = sidebar.find(class_="sidebar-header") + assert sidebar_header is not None, "Missing .sidebar-header inside #session-sidebar" + # .sidebar-title with text 'Sessions' + sidebar_title = sidebar_header.find(class_="sidebar-title") + assert sidebar_title is not None, "Missing .sidebar-title inside .sidebar-header" + assert "Sessions" in sidebar_title.get_text(), ( + f".sidebar-title text must contain 'Sessions', got: {sidebar_title.get_text()!r}" + ) + # #sidebar-collapse-btn + collapse_btn = sidebar_header.find(id="sidebar-collapse-btn") + assert collapse_btn is not None, "Missing #sidebar-collapse-btn inside .sidebar-header" + # #sidebar-list + sidebar_list = sidebar.find(id="sidebar-list") + assert sidebar_list is not None, "Missing #sidebar-list inside #session-sidebar" + + def test_html_element_classes() -> None: """Critical and important elements must carry their CSS styling classes.""" soup = _SOUP diff --git a/frontend/index.html b/frontend/index.html index 1846fd6..86fe5c0 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -16,7 +16,7 @@ - +

muxplex

@@ -26,18 +26,28 @@
- + - + - + - + - + - +