From 5069676914055714c73d0c3f3db0a74a01197a8a Mon Sep 17 00:00:00 2001 From: Ken Date: Tue, 26 May 2026 19:08:49 +0000 Subject: [PATCH] feat: global styles with Tabler dark theme, 3-column layout --- frontend/src/styles/globals.css | 300 ++++++++++++++++++++++++++++++++ tests/test_globals_css.py | 223 ++++++++++++++++++++++++ 2 files changed, 523 insertions(+) create mode 100644 frontend/src/styles/globals.css create mode 100644 tests/test_globals_css.py diff --git a/frontend/src/styles/globals.css b/frontend/src/styles/globals.css new file mode 100644 index 0000000..c11340b --- /dev/null +++ b/frontend/src/styles/globals.css @@ -0,0 +1,300 @@ +/* Tabler CSS imports */ +@import "@tabler/core/dist/css/tabler.min.css"; +@import "@tabler/core/dist/css/tabler-vendors.min.css"; + +/* Force dark theme */ +:root { + color-scheme: dark; +} + +/* Base styles */ +body { + background: #1a1a2e; + color: #e0e0e0; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, + "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + margin: 0; + overflow: hidden; + height: 100vh; +} + +#root { + height: 100vh; + display: flex; +} + +/* ============================================================ + 3-COLUMN LAYOUT + ============================================================ */ + +.app-layout { + display: flex; + width: 100%; + height: 100vh; +} + +/* Left sidebar */ +.sidebar { + width: 240px; + min-width: 240px; + background: #16213e; + border-right: 1px solid #2a2a4a; + display: flex; + flex-direction: column; + overflow-y: auto; +} + +/* Centre chat panel */ +.chat-panel { + flex: 1; + display: flex; + flex-direction: column; + min-width: 0; +} + +/* Right panel */ +.right-panel { + width: 45%; + min-width: 300px; + background: #16213e; + border-left: 1px solid #2a2a4a; + display: flex; + flex-direction: column; +} + +/* ============================================================ + SIDEBAR COMPONENTS + ============================================================ */ + +.sidebar-header { + padding: 1rem; + border-bottom: 1px solid #2a2a4a; +} + +.sidebar-header h3 { + font-size: 0.9rem; + text-transform: uppercase; + color: #8888aa; + letter-spacing: 0.08em; + margin: 0; +} + +.session-list { + flex: 1; + overflow-y: auto; +} + +.session-item { + padding: 0.6rem 0.8rem; + border-radius: 6px; + cursor: pointer; + font-size: 0.85rem; + color: #c0c0d0; +} + +.session-item:hover { + background: #1e2a4a; +} + +.session-item.active { + background: #2a3a5a; + color: #fff; +} + +.session-meta { + font-size: 0.7rem; + color: #6a6a8a; +} + +/* ============================================================ + CHAT COMPONENTS + ============================================================ */ + +.chat-messages { + flex: 1; + overflow-y: auto; + padding: 1rem; +} + +.chat-message { + max-width: 85%; + margin-bottom: 0.75rem; +} + +.chat-message.user { + margin-left: auto; + background: #2a3a6a; + border-radius: 12px 12px 4px 12px; + padding: 0.8rem 1rem; +} + +.chat-message.assistant { + background: #1e2a4a; + border-radius: 12px 12px 12px 4px; + padding: 0.8rem 1rem; +} + +.chat-message.assistant p { + margin: 0.3rem 0; + line-height: 1.5; +} + +.chat-input-area { + padding: 1rem; + border-top: 1px solid #2a2a4a; + display: flex; + gap: 0.5rem; +} + +.chat-input-area input, +.chat-input-area textarea { + background: #1e2a4a; + border: 1px solid #3a3a5a; + border-radius: 8px; + color: #e0e0e0; + flex: 1; + padding: 0.6rem 0.8rem; +} + +.chat-input-area input:focus, +.chat-input-area textarea:focus { + border-color: #5a7aba; + outline: none; +} + +.chat-input-area button { + background: #3a5aba; + color: #fff; + border: none; + border-radius: 8px; + padding: 0.6rem 1.2rem; + cursor: pointer; +} + +.chat-input-area button:disabled { + opacity: 0.5; + cursor: not-allowed; +} + +/* ============================================================ + TOOL CALL CARDS + ============================================================ */ + +.tool-call-card { + background: #12192e; + border: 1px solid #2a2a4a; + border-radius: 8px; + padding: 0.6rem 0.8rem; + margin: 0.5rem 0; + font-size: 0.8rem; +} + +.tool-name { + color: #7aa2f7; + font-weight: 600; + font-family: monospace; +} + +.tool-args { + color: #8888aa; + font-family: monospace; + font-size: 0.75rem; + white-space: pre-wrap; + word-break: break-all; +} + +.tool-status { + display: inline-block; + padding: 1px 6px; + border-radius: 4px; + font-size: 0.7rem; + margin-left: 0.5rem; +} + +.tool-status.running { + background: #2a4a3a; + color: #7af7a2; +} + +.tool-status.complete { + background: #2a3a4a; + color: #7aa2f7; +} + +/* ============================================================ + RIGHT PANEL TABS + ============================================================ */ + +.right-panel-tabs { + display: flex; + border-bottom: 1px solid #2a2a4a; +} + +.right-panel-tabs button { + flex: 1; + padding: 0.6rem; + background: transparent; + border: none; + border-bottom: 2px solid transparent; + color: #8888aa; + cursor: pointer; + font-size: 0.85rem; +} + +.right-panel-tabs button.active { + color: #fff; + border-bottom-color: #5a7aba; +} + +.right-panel-content { + flex: 1; + overflow-y: auto; +} + +/* ============================================================ + LOGIN PAGE + ============================================================ */ + +.login-page { + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + width: 100%; +} + +.login-card { + background: #16213e; + border: 1px solid #2a2a4a; + border-radius: 12px; + padding: 2rem; + width: 360px; +} + +.login-card input { + width: 100%; + padding: 0.6rem; + margin-bottom: 0.8rem; + background: #1e2a4a; + border: 1px solid #3a3a5a; + border-radius: 6px; + color: #e0e0e0; + font-size: 0.9rem; + box-sizing: border-box; +} + +.login-card button { + width: 100%; + padding: 0.7rem; + background: #3a5aba; + color: #fff; + border: none; + border-radius: 6px; + cursor: pointer; +} + +.error { + color: #f7607a; + font-size: 0.85rem; + text-align: center; +} diff --git a/tests/test_globals_css.py b/tests/test_globals_css.py new file mode 100644 index 0000000..4c77882 --- /dev/null +++ b/tests/test_globals_css.py @@ -0,0 +1,223 @@ +""" +Tests for frontend/src/styles/globals.css + +Verifies the CSS file exists with all required selectors, imports, and styles +as specified in task-3. +""" + +import os +import pytest + +CSS_FILE = os.path.join( + os.path.dirname(__file__), + "..", + "frontend", + "src", + "styles", + "globals.css", +) + + +@pytest.fixture(scope="module") +def css_content(): + assert os.path.exists(CSS_FILE), f"globals.css not found at {CSS_FILE}" + with open(CSS_FILE) as f: + return f.read() + + +class TestTablerImports: + def test_imports_tabler_core_css(self, css_content): + assert "@tabler/core/dist/css/tabler.min.css" in css_content + + def test_imports_tabler_vendors_css(self, css_content): + assert "tabler-vendors.min.css" in css_content + + +class TestColorScheme: + def test_forces_dark_color_scheme(self, css_content): + assert "color-scheme: dark" in css_content + + +class TestBodyStyles: + def test_body_background_color(self, css_content): + assert "#1a1a2e" in css_content + + def test_body_text_color(self, css_content): + assert "#e0e0e0" in css_content + + def test_body_margin_zero(self, css_content): + assert "margin: 0" in css_content + + def test_body_overflow_hidden(self, css_content): + assert "overflow: hidden" in css_content + + def test_body_height_100vh(self, css_content): + # body should have height: 100vh + assert "100vh" in css_content + + +class TestRootStyles: + def test_root_height_100vh(self, css_content): + assert "#root" in css_content + assert "100vh" in css_content + + def test_root_display_flex(self, css_content): + assert "#root" in css_content + assert "flex" in css_content + + +class TestLayoutClasses: + def test_app_layout_class(self, css_content): + assert ".app-layout" in css_content + + def test_sidebar_class(self, css_content): + assert ".sidebar" in css_content + + def test_sidebar_width(self, css_content): + assert "240px" in css_content + + def test_sidebar_background(self, css_content): + assert "#16213e" in css_content + + def test_chat_panel_class(self, css_content): + assert ".chat-panel" in css_content + + def test_right_panel_class(self, css_content): + assert ".right-panel" in css_content + + def test_right_panel_width_45_percent(self, css_content): + assert "45%" in css_content + + def test_right_panel_min_width(self, css_content): + assert "300px" in css_content + + +class TestSidebarComponents: + def test_sidebar_header(self, css_content): + assert ".sidebar-header" in css_content + + def test_session_list(self, css_content): + assert ".session-list" in css_content + + def test_session_item(self, css_content): + assert ".session-item" in css_content + + def test_session_item_hover(self, css_content): + assert ".session-item:hover" in css_content + + def test_session_item_active(self, css_content): + assert ".session-item.active" in css_content + + def test_session_meta(self, css_content): + assert ".session-meta" in css_content + + def test_sidebar_h3_color(self, css_content): + assert "#8888aa" in css_content + + def test_session_item_color(self, css_content): + assert "#c0c0d0" in css_content + + def test_session_meta_color(self, css_content): + assert "#6a6a8a" in css_content + + +class TestChatComponents: + def test_chat_messages_class(self, css_content): + assert ".chat-messages" in css_content + + def test_chat_message_class(self, css_content): + assert ".chat-message" in css_content + + def test_chat_message_max_width(self, css_content): + assert "85%" in css_content + + def test_chat_message_user_class(self, css_content): + assert ".chat-message.user" in css_content + + def test_chat_message_user_background(self, css_content): + assert "#2a3a6a" in css_content + + def test_chat_message_assistant_class(self, css_content): + assert ".chat-message.assistant" in css_content + + def test_chat_message_assistant_background(self, css_content): + assert "#1e2a4a" in css_content + + def test_chat_input_area_class(self, css_content): + assert ".chat-input-area" in css_content + + def test_chat_input_border_color(self, css_content): + assert "#3a3a5a" in css_content + + def test_chat_input_focus_border(self, css_content): + assert "#5a7aba" in css_content + + def test_chat_button_background(self, css_content): + assert "#3a5aba" in css_content + + +class TestToolCallCards: + def test_tool_call_card_class(self, css_content): + assert ".tool-call-card" in css_content + + def test_tool_call_card_background(self, css_content): + assert "#12192e" in css_content + + def test_tool_name_class(self, css_content): + assert ".tool-name" in css_content + + def test_tool_name_color(self, css_content): + assert "#7aa2f7" in css_content + + def test_tool_args_class(self, css_content): + assert ".tool-args" in css_content + + def test_tool_args_color(self, css_content): + assert "#8888aa" in css_content + + def test_tool_status_class(self, css_content): + assert ".tool-status" in css_content + + def test_tool_status_running(self, css_content): + assert ".running" in css_content + + def test_tool_status_running_color(self, css_content): + assert "#7af7a2" in css_content + + def test_tool_status_complete(self, css_content): + assert ".complete" in css_content + + def test_tool_status_complete_color(self, css_content): + assert "#7aa2f7" in css_content + + +class TestRightPanelTabs: + def test_right_panel_tabs_class(self, css_content): + assert ".right-panel-tabs" in css_content + + def test_right_panel_content_class(self, css_content): + assert ".right-panel-content" in css_content + + +class TestLoginPage: + def test_login_page_class(self, css_content): + assert ".login-page" in css_content + + def test_login_card_class(self, css_content): + assert ".login-card" in css_content + + def test_login_card_width(self, css_content): + assert "360px" in css_content + + def test_login_card_background(self, css_content): + # Already tested via sidebar bg, but login card also uses #16213e + assert "#16213e" in css_content + + def test_login_card_border_radius(self, css_content): + assert "12px" in css_content + + def test_login_error_class(self, css_content): + assert ".error" in css_content + + def test_login_error_color(self, css_content): + assert "#f7607a" in css_content