From c3ff2bdebd3a838f233bd1abf6cb8b6ee15bbe17 Mon Sep 17 00:00:00 2001 From: Dave Date: Thu, 25 Dec 2025 12:58:37 +0000 Subject: [PATCH] feat: ui polish (sticky header, dark mode, flat inputs) --- .living_spec/specs/functional/UI_LAYOUT.md | 33 ++ .../stories/06_fix_ui_responsiveness.md | 19 - .../stories/07_ui_polish_sticky_header.md | 17 + src/App.css | 124 +++--- src/App.tsx | 40 +- src/components/Chat.tsx | 402 +++++++++++++----- 6 files changed, 407 insertions(+), 228 deletions(-) create mode 100644 .living_spec/specs/functional/UI_LAYOUT.md delete mode 100644 .living_spec/stories/06_fix_ui_responsiveness.md create mode 100644 .living_spec/stories/07_ui_polish_sticky_header.md diff --git a/.living_spec/specs/functional/UI_LAYOUT.md b/.living_spec/specs/functional/UI_LAYOUT.md new file mode 100644 index 0000000..9993294 --- /dev/null +++ b/.living_spec/specs/functional/UI_LAYOUT.md @@ -0,0 +1,33 @@ +# Functional Spec: UI Layout + +## 1. Global Structure +The application uses a **fixed-layout** strategy to maximize chat visibility. + +```text ++-------------------------------------------------------+ +| HEADER (Fixed Height, e.g., 50px) | +| [Project: ~/foo/bar] [Model: llama3] [x] Tools | ++-------------------------------------------------------+ +| | +| CHAT AREA (Flex Grow, Scrollable) | +| | +| (User Message) | +| (Agent Message) | +| | ++-------------------------------------------------------+ +| INPUT AREA (Fixed Height, Bottom) | +| [ Input Field ........................... ] [Send] | ++-------------------------------------------------------+ +``` + +## 2. Components +* **Header:** Contains global context (Project) and session config (Model/Tools). + * *Constraint:* Must not scroll away. +* **ChatList:** The scrollable container for messages. +* **InputBar:** Pinned to the bottom. + +## 3. Styling +* Use Flexbox (`flex-direction: column`) on the main container. +* Header: `flex-shrink: 0`. +* ChatList: `flex-grow: 1`, `overflow-y: auto`. +* InputBar: `flex-shrink: 0`. diff --git a/.living_spec/stories/06_fix_ui_responsiveness.md b/.living_spec/stories/06_fix_ui_responsiveness.md deleted file mode 100644 index b6988ba..0000000 --- a/.living_spec/stories/06_fix_ui_responsiveness.md +++ /dev/null @@ -1,19 +0,0 @@ -# Story: Fix UI Responsiveness (Tech Debt) - -## User Story -**As a** User -**I want** the UI to remain interactive and responsive while the Agent is thinking or executing tools -**So that** I don't feel like the application has crashed. - -## Context -Currently, the UI locks up or becomes unresponsive during long LLM generations or tool executions. Even though the backend commands are async, the frontend experience degrades. - -## Acceptance Criteria -* [ ] Investigate the root cause of the freezing (JS Main Thread blocking vs. Tauri IPC blocking). -* [ ] Implement a "Streaming" architecture for Chat if necessary (getting partial tokens instead of waiting for full response). - * *Note: This might overlap with future streaming stories, but basic responsiveness is the priority here.* -* [ ] Add visual indicators (Spinner/Progress Bar) that animate smoothly during the wait. -* [ ] Ensure the "Stop Generation" button (if added) can actually interrupt the backend task. - -## Out of Scope -* Full streaming text (unless that is the only way to fix the freezing). diff --git a/.living_spec/stories/07_ui_polish_sticky_header.md b/.living_spec/stories/07_ui_polish_sticky_header.md new file mode 100644 index 0000000..81ab7ac --- /dev/null +++ b/.living_spec/stories/07_ui_polish_sticky_header.md @@ -0,0 +1,17 @@ +# Story: UI Polish - Sticky Header & Compact Layout + +## User Story +**As a** User +**I want** key controls (Model Selection, Tool Toggle, Project Path) to be visible at all times +**So that** I don't have to scroll up to check my configuration or change settings. + +## Acceptance Criteria +* [ ] Frontend: Create a fixed `
` component at the top of the viewport. +* [ ] Frontend: Move "Active Project" display into this header (make it compact/truncated if long). +* [ ] Frontend: Move "Ollama Model" and "Enable Tools" controls into this header. +* [ ] Frontend: Ensure the Chat message list scrolls *under* the header (taking up remaining height). +* [ ] Frontend: Remove the redundant "Active Project" bar from the main workspace area. + +## Out of Scope +* Full visual redesign (just layout fixing). +* Settings modal (keep controls inline for now). diff --git a/src/App.css b/src/App.css index 85f7a4a..3b0f5fb 100644 --- a/src/App.css +++ b/src/App.css @@ -1,116 +1,116 @@ .logo.vite:hover { - filter: drop-shadow(0 0 2em #747bff); + filter: drop-shadow(0 0 2em #747bff); } .logo.react:hover { - filter: drop-shadow(0 0 2em #61dafb); + filter: drop-shadow(0 0 2em #61dafb); } :root { - font-family: Inter, Avenir, Helvetica, Arial, sans-serif; - font-size: 16px; - line-height: 24px; - font-weight: 400; + font-family: Inter, Avenir, Helvetica, Arial, sans-serif; + font-size: 16px; + line-height: 24px; + font-weight: 400; - color: #0f0f0f; - background-color: #f6f6f6; + color: #0f0f0f; + background-color: #f6f6f6; - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - -webkit-text-size-adjust: 100%; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + -webkit-text-size-adjust: 100%; } .container { - margin: 0; - padding-top: 10vh; - display: flex; - flex-direction: column; - justify-content: center; - text-align: center; + margin: 0; + padding-top: 10vh; + display: flex; + flex-direction: column; + justify-content: center; + text-align: center; } .logo { - height: 6em; - padding: 1.5em; - will-change: filter; - transition: 0.75s; + height: 6em; + padding: 1.5em; + will-change: filter; + transition: 0.75s; } .logo.tauri:hover { - filter: drop-shadow(0 0 2em #24c8db); + filter: drop-shadow(0 0 2em #24c8db); } .row { - display: flex; - justify-content: center; + display: flex; + justify-content: center; } a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; + font-weight: 500; + color: #646cff; + text-decoration: inherit; } a:hover { - color: #535bf2; + color: #535bf2; } h1 { - text-align: center; + text-align: center; } input, button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - color: #0f0f0f; - background-color: #ffffff; - transition: border-color 0.25s; - box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); + border-radius: 8px; + border: 1px solid transparent; + padding: 0.6em 1.2em; + font-size: 1em; + font-weight: 500; + font-family: inherit; + color: #0f0f0f; + background-color: #ffffff; + transition: border-color 0.25s; + box-shadow: 0 2px 2px rgba(0, 0, 0, 0.2); } button { - cursor: pointer; + cursor: pointer; } button:hover { - border-color: #396cd8; + border-color: #396cd8; } button:active { - border-color: #396cd8; - background-color: #e8e8e8; + border-color: #396cd8; + background-color: #e8e8e8; } input, button { - outline: none; + outline: none; } #greet-input { - margin-right: 5px; + margin-right: 5px; } @media (prefers-color-scheme: dark) { - :root { - color: #f6f6f6; - background-color: #2f2f2f; - } + :root { + color: #f6f6f6; + background-color: #2f2f2f; + } - a:hover { - color: #24c8db; - } + a:hover { + color: #24c8db; + } - input, - button { - color: #ffffff; - background-color: #0f0f0f98; - } - button:active { - background-color: #0f0f0f69; - } + input, + button { + color: #ffffff; + background-color: #0f0f0f98; + } + button:active { + background-color: #0f0f0f69; + } } diff --git a/src/App.tsx b/src/App.tsx index ccd30fa..f9f8250 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -54,11 +54,16 @@ function App() { } return ( -
-

AI Code Assistant

- +
{!projectPath ? ( -
+
+

AI Code Assistant

Please select a project folder to start the Story-Driven Spec Workflow. @@ -66,31 +71,8 @@ function App() {

) : ( -
-
- - Active Project: {projectPath} - - -
-
- +
+
)} diff --git a/src/components/Chat.tsx b/src/components/Chat.tsx index f4f2abd..f2b3f99 100644 --- a/src/components/Chat.tsx +++ b/src/components/Chat.tsx @@ -4,7 +4,12 @@ import { listen } from "@tauri-apps/api/event"; import Markdown from "react-markdown"; import { Message, ProviderConfig } from "../types"; -export function Chat() { +interface ChatProps { + projectPath: string; + onCloseProject: () => void; +} + +export function Chat({ projectPath, onCloseProject }: ChatProps) { const [messages, setMessages] = useState([]); const [input, setInput] = useState(""); const [loading, setLoading] = useState(false); @@ -86,56 +91,136 @@ export function Chat() { display: "flex", flexDirection: "column", height: "100%", - maxWidth: "800px", - margin: "0 auto", + backgroundColor: "#171717", + color: "#ececec", }} > - {/* Settings Bar */} + {/* Sticky Header */}
- - {availableModels.length > 0 ? ( - - ) : ( - setModel(e.target.value)} - placeholder="e.g. llama3, mistral" - style={{ padding: "5px" }} - /> - )} - +
+ {projectPath} +
+ +
+ + {/* Model Controls */} +
+ {availableModels.length > 0 ? ( + + ) : ( + setModel(e.target.value)} + placeholder="Model" + style={{ + padding: "6px 12px", + borderRadius: "99px", + border: "none", + fontSize: "0.9em", + background: "#2f2f2f", + color: "#ececec", + outline: "none", + }} + /> + )} + +
{/* Messages Area */} @@ -143,109 +228,190 @@ export function Chat() { style={{ flex: 1, overflowY: "auto", - padding: "20px", + padding: "20px 0", display: "flex", flexDirection: "column", - gap: "15px", + gap: "24px", }} > - {messages.map((msg, idx) => ( -
- - {msg.role === "user" - ? "You" - : msg.role === "tool" - ? "Tool Output" - : "Agent"} - - {msg.role === "tool" ? ( -
- {msg.content} -
- ) : ( - {msg.content} - )} - - {/* Show Tool Calls if present */} - {msg.tool_calls && ( +
+ {messages.map((msg, idx) => ( +
- {msg.tool_calls.map((tc, i) => ( + {msg.role === "user" ? ( + msg.content + ) : msg.role === "tool" ? ( +
+ + Tool Output + +
+ {msg.content} +
+
+ ) : ( +
+ {/* Assuming global CSS handles standard markdown styling now */} + {msg.content} +
+ )} + + {/* Show Tool Calls if present */} + {msg.tool_calls && (
- 🛠{" "} - - {tc.function.name}({tc.function.arguments}) - + {msg.tool_calls.map((tc, i) => ( +
+ Running: + + {tc.function.name} + +
+ ))}
- ))} + )}
- )} -
- ))} - {loading && ( -
- Thinking... -
- )} -
+
+ ))} + {loading && ( +
+ Thinking... +
+ )} +
+
{/* Input Area */}
- setInput(e.target.value)} - onKeyDown={(e) => e.key === "Enter" && sendMessage()} - placeholder="Ask the agent to do something..." +
- + setInput(e.target.value)} + onKeyDown={(e) => e.key === "Enter" && sendMessage()} + placeholder="Send a message..." + style={{ + width: "100%", + padding: "14px 20px", + paddingRight: "50px", // space for button + borderRadius: "24px", + border: "1px solid #333", + outline: "none", + fontSize: "1rem", + fontWeight: "500", + background: "#2f2f2f", + color: "#ececec", + boxShadow: "0 2px 6px rgba(0,0,0,0.02)", + }} + /> + +
);