diff --git a/coordinator/tests/test_frontend_css.py b/coordinator/tests/test_frontend_css.py index 4af29d5..ce7f851 100644 --- a/coordinator/tests/test_frontend_css.py +++ b/coordinator/tests/test_frontend_css.py @@ -135,3 +135,165 @@ def test_css_terminal_container_min_width(): assert "overflow: hidden" in block assert "background: #000" in block assert "padding: 0 4px" in block + + +# ============================================================ +# Sidebar container and collapse animation (task-3) +# ============================================================ + + +def _extract_rule_block(css: str, selector: str) -> str: + """Extract the CSS block for a given selector.""" + idx = css.index(selector) + block_start = css.index("{", idx) + block_end = css.index("}", block_start) + return css[block_start:block_end] + + +def test_css_session_sidebar_exists(): + """.session-sidebar rule must exist in the CSS.""" + css = read_css() + assert ".session-sidebar" in css + + +def test_css_session_sidebar_dimensions(): + """.session-sidebar must have width: 200px and min-width: 200px.""" + css = read_css() + block = _extract_rule_block(css, ".session-sidebar {") + assert "width: 200px" in block + assert "min-width: 200px" in block + + +def test_css_session_sidebar_background(): + """.session-sidebar must use var(--bg-secondary) background.""" + css = read_css() + block = _extract_rule_block(css, ".session-sidebar {") + assert "background: var(--bg-secondary)" in block + + +def test_css_session_sidebar_border(): + """.session-sidebar must have a border-right using var(--border-subtle).""" + css = read_css() + block = _extract_rule_block(css, ".session-sidebar {") + assert "border-right: 1px solid var(--border-subtle)" in block + + +def test_css_session_sidebar_flex_column(): + """.session-sidebar must use flex column layout with overflow hidden.""" + css = read_css() + block = _extract_rule_block(css, ".session-sidebar {") + assert "display: flex" in block + assert "flex-direction: column" in block + assert "overflow: hidden" in block + assert "flex-shrink: 0" in block + + +def test_css_session_sidebar_transition(): + """.session-sidebar must have transition on width and min-width for collapse animation.""" + css = read_css() + block = _extract_rule_block(css, ".session-sidebar {") + assert "transition:" in block + assert "width 0.25s ease" in block + assert "min-width 0.25s ease" in block + + +def test_css_sidebar_collapsed_state(): + """.session-sidebar.sidebar--collapsed must set width and min-width to 0.""" + css = read_css() + assert ".session-sidebar.sidebar--collapsed" in css + block = _extract_rule_block(css, ".session-sidebar.sidebar--collapsed") + assert "width: 0" in block + assert "min-width: 0" in block + + +def test_css_sidebar_before_terminal_container(): + """.session-sidebar rules must appear after .view-body and before .terminal-container.""" + css = read_css() + assert ".session-sidebar" in css + assert ".view-body" in css + assert ".terminal-container" in css + view_body_idx = css.index(".view-body") + sidebar_idx = css.index(".session-sidebar") + terminal_idx = css.index(".terminal-container") + assert view_body_idx < sidebar_idx, ".session-sidebar must come after .view-body" + assert sidebar_idx < terminal_idx, ".session-sidebar must come before .terminal-container" + + +def test_css_sidebar_header(): + """.sidebar-header must be flex row with space-between and padding.""" + css = read_css() + assert ".sidebar-header" in css + block = _extract_rule_block(css, ".sidebar-header {") + assert "display: flex" in block + assert "flex-direction: row" in block + assert "justify-content: space-between" in block + assert "padding: 8px 12px" in block + assert "border-bottom" in block + + +def test_css_sidebar_title(): + """.sidebar-title must be styled as a small uppercase label.""" + css = read_css() + assert ".sidebar-title" in css + block = _extract_rule_block(css, ".sidebar-title {") + assert "font-size: 11px" in block + assert "font-weight: 600" in block + assert "text-transform: uppercase" in block + assert "letter-spacing:" in block + assert "color: var(--text-muted)" in block + + +def test_css_sidebar_list(): + """.sidebar-list must scroll vertically and fill remaining height.""" + css = read_css() + assert ".sidebar-list" in css + block = _extract_rule_block(css, ".sidebar-list {") + assert "flex: 1" in block + assert "overflow-y: auto" in block + assert "overflow-x: hidden" in block + + +def test_css_sidebar_collapse_btn(): + """.sidebar-collapse-btn must be a minimal button styled for the chevron.""" + css = read_css() + assert ".sidebar-collapse-btn" in css + block = _extract_rule_block(css, ".sidebar-collapse-btn {") + assert "background: none" in block + assert "border: none" in block + assert "color: var(--text-muted)" in block + assert "cursor: pointer" in block + assert "font-size: 18px" in block + assert "padding: 2px 6px" in block + assert "border-radius: 4px" in block + + +def test_css_sidebar_collapse_btn_hover(): + """.sidebar-collapse-btn:hover must show full text color.""" + css = read_css() + assert ".sidebar-collapse-btn:hover" in css + block = _extract_rule_block(css, ".sidebar-collapse-btn:hover {") + assert "color: var(--text)" in block + + +def test_css_sidebar_toggle_btn(): + """.sidebar-toggle-btn must be a 36x36 bordered button with flex centering.""" + css = read_css() + assert ".sidebar-toggle-btn" in css + block = _extract_rule_block(css, ".sidebar-toggle-btn {") + assert "background: none" in block + assert "border: 1px solid var(--border)" in block + assert "border-radius: 4px" in block + assert "width: 36px" in block + assert "height: 36px" in block + assert "display: flex" in block + assert "align-items: center" in block + assert "justify-content: center" in block + assert "margin-right: 8px" in block + + +def test_css_sidebar_toggle_btn_hover(): + """.sidebar-toggle-btn:hover must update border-color to accent.""" + css = read_css() + assert ".sidebar-toggle-btn:hover" in css + block = _extract_rule_block(css, ".sidebar-toggle-btn:hover {") + assert "border-color: var(--accent)" in block diff --git a/frontend/style.css b/frontend/style.css index 1c7748c..4b49623 100644 --- a/frontend/style.css +++ b/frontend/style.css @@ -373,6 +373,83 @@ body { overflow: hidden; } +/* ============================================================ + Session sidebar + ============================================================ */ + +.session-sidebar { + width: 200px; + min-width: 200px; + background: var(--bg-secondary); + border-right: 1px solid var(--border-subtle); + display: flex; + flex-direction: column; + overflow: hidden; + flex-shrink: 0; + transition: width 0.25s ease, min-width 0.25s ease; +} + +.session-sidebar.sidebar--collapsed { + width: 0; + min-width: 0; +} + +.sidebar-header { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 8px 12px; + border-bottom: 1px solid var(--border-subtle); + flex-shrink: 0; +} + +.sidebar-title { + font-size: 11px; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.06em; + color: var(--text-muted); +} + +.sidebar-list { + flex: 1; + overflow-y: auto; + overflow-x: hidden; +} + +.sidebar-collapse-btn { + background: none; + border: none; + color: var(--text-muted); + cursor: pointer; + font-size: 18px; + padding: 2px 6px; + border-radius: 4px; +} + +.sidebar-collapse-btn:hover { + color: var(--text); +} + +.sidebar-toggle-btn { + background: none; + border: 1px solid var(--border); + border-radius: 4px; + width: 36px; + height: 36px; + display: flex; + align-items: center; + justify-content: center; + margin-right: 8px; + cursor: pointer; + color: var(--text-muted); +} + +.sidebar-toggle-btn:hover { + border-color: var(--accent); +} + .terminal-container { flex: 1; min-width: 0;