From ed15279dae5f381aa11c7e91f0d0525f1aa5dac9 Mon Sep 17 00:00:00 2001 From: Dave Date: Thu, 25 Dec 2025 15:23:49 +0000 Subject: [PATCH] feat: Story 9 - Remove scroll bars for cleaner UI Implemented Story 9: Hidden Scroll Bars - Added CSS to hide scroll bars globally while maintaining functionality - Used scrollbar-width: none for Firefox - Used ::-webkit-scrollbar { display: none; } for Chrome/Safari/Edge - Set overflow-x: hidden to prevent unwanted horizontal scrolling - Maintained full scroll functionality (mouse wheel, trackpad, keyboard) Updated Specs - Added Scroll Bar Styling section to UI_UX.md - Documented cross-browser compatibility requirements - Specified areas affected and implementation approach Bug Fixes - Fixed biome linting issues in App.tsx (import organization, button type) - Applied biome formatter for consistent code style --- .living_spec/specs/functional/UI_UX.md | 29 ++++ .living_spec/stories/09_remove_scroll_bars.md | 3 - .../13_stop_button_on_model_response.md | 0 .../stories/archive/09_remove_scroll_bars.md | 27 ++++ package.json | 10 +- src/App.css | 17 ++ src/App.test.tsx | 25 +++ src/App.tsx | 146 +++++++++--------- 8 files changed, 180 insertions(+), 77 deletions(-) delete mode 100644 .living_spec/stories/09_remove_scroll_bars.md create mode 100644 .living_spec/stories/13_stop_button_on_model_response.md create mode 100644 .living_spec/stories/archive/09_remove_scroll_bars.md create mode 100644 src/App.test.tsx diff --git a/.living_spec/specs/functional/UI_UX.md b/.living_spec/specs/functional/UI_UX.md index 4bc2713..5115112 100644 --- a/.living_spec/specs/functional/UI_UX.md +++ b/.living_spec/specs/functional/UI_UX.md @@ -53,3 +53,32 @@ Tool outputs should be rendered in a collapsible component that is **closed by d * Or implement custom collapsible component with proper ARIA attributes * Tool outputs should be visually distinct (border, background color, or badge) * Multiple tool calls in sequence should each be independently collapsible + +## Scroll Bar Styling + +### Problem +Visible scroll bars create visual clutter and make the interface feel less polished. Standard browser scroll bars can be distracting and break the clean aesthetic of the dark theme. + +### Solution: Hidden Scroll Bars with Maintained Functionality +Scroll bars should be hidden while maintaining full scroll functionality. + +### Requirements + +1. **Visual:** Scroll bars should not be visible to the user +2. **Functionality:** Scrolling must still work perfectly: + - Mouse wheel scrolling + - Trackpad scrolling + - Keyboard navigation (arrow keys, page up/down) + - Auto-scroll to bottom for new messages +3. **Cross-browser:** Solution must work on Chrome, Firefox, and Safari +4. **Areas affected:** + - Main chat message area (vertical scroll) + - Tool output content (both vertical and horizontal) + - Any other scrollable containers + +### Implementation Notes +* Use CSS `scrollbar-width: none` for Firefox +* Use `::-webkit-scrollbar { display: none; }` for Chrome/Safari/Edge +* Maintain `overflow: auto` or `overflow-y: scroll` to preserve scroll functionality +* Ensure `overflow-x: hidden` where horizontal scroll is not needed +* Test with very long messages and large tool outputs to ensure no layout breaking diff --git a/.living_spec/stories/09_remove_scroll_bars.md b/.living_spec/stories/09_remove_scroll_bars.md deleted file mode 100644 index d6f65f1..0000000 --- a/.living_spec/stories/09_remove_scroll_bars.md +++ /dev/null @@ -1,3 +0,0 @@ -there is a scroll bar on the right that looks gross. also a horizontal scroll bar that should come out - -story needs to be worked through diff --git a/.living_spec/stories/13_stop_button_on_model_response.md b/.living_spec/stories/13_stop_button_on_model_response.md new file mode 100644 index 0000000..e69de29 diff --git a/.living_spec/stories/archive/09_remove_scroll_bars.md b/.living_spec/stories/archive/09_remove_scroll_bars.md new file mode 100644 index 0000000..4fc8e76 --- /dev/null +++ b/.living_spec/stories/archive/09_remove_scroll_bars.md @@ -0,0 +1,27 @@ +# Story: Remove Unnecessary Scroll Bars + +## User Story +**As a** User +**I want** the UI to have clean, minimal scrolling without visible scroll bars +**So that** the interface looks polished and doesn't have distracting visual clutter. + +## Acceptance Criteria +* [x] Remove or hide the vertical scroll bar on the right side of the chat area +* [x] Remove or hide any horizontal scroll bars that appear +* [x] Maintain scrolling functionality (content should still be scrollable, just without visible bars) +* [x] Consider using overlay scroll bars or auto-hiding scroll bars for better aesthetics +* [x] Ensure the solution works across different browsers (Chrome, Firefox, Safari) +* [x] Verify that long messages and tool outputs still scroll properly + +## Out of Scope +* Custom scroll bar designs with fancy styling +* Touch/gesture scrolling improvements for mobile (desktop focus for now) + +## Implementation Notes +* Use CSS `scrollbar-width: none` for Firefox +* Use `::-webkit-scrollbar { display: none; }` for Chrome/Safari +* Ensure `overflow: auto` or `overflow-y: scroll` is still applied to maintain scroll functionality +* Test with long tool outputs and chat histories to ensure no layout breaking + +## Related Functional Specs +* Functional Spec: UI/UX \ No newline at end of file diff --git a/package.json b/package.json index 139d3f6..fb9eab0 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "dev": "vite", "build": "tsc && vite build", "preview": "vite preview", - "tauri": "tauri" + "tauri": "tauri", + "test": "jest" }, "dependencies": { "@tauri-apps/api": "^2", @@ -24,6 +25,11 @@ "@types/react-dom": "^19.1.6", "@vitejs/plugin-react": "^4.6.0", "typescript": "~5.8.3", - "vite": "^7.0.4" + "vite": "^7.0.4", + "jest": "^29.0.0", + "ts-jest": "^29.0.0", + "@testing-library/react": "^14.0.0", + "@testing-library/jest-dom": "^6.0.0", + "@testing-library/user-event": "^14.4.3" } } diff --git a/src/App.css b/src/App.css index 8f65c02..8852579 100644 --- a/src/App.css +++ b/src/App.css @@ -158,3 +158,20 @@ details summary span:first-child { background: transparent; padding: 0; } + +/* Hide scroll bars globally while maintaining scroll functionality */ +/* Firefox */ +* { + scrollbar-width: none; +} + +/* Chrome, Safari, Edge */ +*::-webkit-scrollbar { + display: none; +} + +/* Ensure scroll functionality is maintained */ +body, +html { + overflow-x: hidden; +} diff --git a/src/App.test.tsx b/src/App.test.tsx new file mode 100644 index 0000000..95c10cc --- /dev/null +++ b/src/App.test.tsx @@ -0,0 +1,25 @@ +import { render, screen, fireEvent } from "@testing-library/react"; +import App from "./App"; + +// Since the App component relies on Tauri APIs, we mock them to isolate tests +jest.mock("@tauri-apps/api/core", () => ({ + invoke: jest.fn().mockResolvedValue(null), +})); + +jest.mock("@tauri-apps/plugin-dialog", () => ({ + open: jest.fn().mockResolvedValue("/tmp/project"), +})); + +test("renders without crashing", () => { + render(); + expect(screen.getByText("AI Code Assistant")).toBeInTheDocument(); +}); + +it("opens project directory button calls open", async () => { + const { open } = require("@tauri-apps/plugin-dialog"); + render(); + const button = screen.getByText("Open Project Directory"); + fireEvent.click(button); + await Promise.resolve(); // wait for async open + expect(open).toHaveBeenCalled(); +}); diff --git a/src/App.tsx b/src/App.tsx index f9f8250..bd6fc2f 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,88 +1,90 @@ -import { useState, useEffect } from "react"; import { invoke } from "@tauri-apps/api/core"; import { open } from "@tauri-apps/plugin-dialog"; +import { useEffect, useState } from "react"; import { Chat } from "./components/Chat"; import "./App.css"; function App() { - const [projectPath, setProjectPath] = useState(null); - const [errorMsg, setErrorMsg] = useState(null); + const [projectPath, setProjectPath] = useState(null); + const [errorMsg, setErrorMsg] = useState(null); - useEffect(() => { - invoke("get_current_project") - .then((path) => { - if (path) setProjectPath(path); - }) - .catch((e) => console.error(e)); - }, []); + useEffect(() => { + invoke("get_current_project") + .then((path) => { + if (path) setProjectPath(path); + }) + .catch((e) => console.error(e)); + }, []); - async function closeProject() { - try { - await invoke("close_project"); - setProjectPath(null); - } catch (e) { - console.error(e); - } - } + async function closeProject() { + try { + await invoke("close_project"); + setProjectPath(null); + } catch (e) { + console.error(e); + } + } - async function selectProject() { - try { - setErrorMsg(null); - // Open native folder picker - const selected = await open({ - directory: true, - multiple: false, - }); + async function selectProject() { + try { + setErrorMsg(null); + // Open native folder picker + const selected = await open({ + directory: true, + multiple: false, + }); - if (selected === null) { - // User cancelled selection - return; - } + if (selected === null) { + // User cancelled selection + return; + } - // Invoke backend command to verify and set state - // Note: invoke argument names must match Rust function args - const confirmedPath = await invoke("open_project", { - path: selected, - }); - setProjectPath(confirmedPath); - } catch (e) { - console.error(e); - setErrorMsg( - typeof e === "string" ? e : "An error occurred opening the project.", - ); - } - } + // Invoke backend command to verify and set state + // Note: invoke argument names must match Rust function args + const confirmedPath = await invoke("open_project", { + path: selected, + }); + setProjectPath(confirmedPath); + } catch (e) { + console.error(e); + setErrorMsg( + typeof e === "string" ? e : "An error occurred opening the project.", + ); + } + } - return ( -
- {!projectPath ? ( -
-

AI Code Assistant

-

- Please select a project folder to start the Story-Driven Spec - Workflow. -

- -
- ) : ( -
- -
- )} + return ( +
+ {!projectPath ? ( +
+

AI Code Assistant

+

+ Please select a project folder to start the Story-Driven Spec + Workflow. +

+ +
+ ) : ( +
+ +
+ )} - {errorMsg && ( -
-

Error: {errorMsg}

-
- )} -
- ); + {errorMsg && ( +
+

Error: {errorMsg}

+
+ )} +
+ ); } export default App;