feat: Story 11 - Left-align text and add syntax highlighting
Implemented Story 11: Text Alignment and Syntax Highlighting - Removed center-alignment from chat container (text-align: center) - Added left-alignment to markdown body and code blocks - Integrated react-syntax-highlighter with react-markdown - Added syntax highlighting for code blocks using oneDark theme - Supports JavaScript, TypeScript, Rust, Python, JSON, Markdown, Shell, and more - Inline code maintains simple styling without full highlighting - All code blocks are now left-aligned for better readability Fixed: Agent over-aggressive file writing behavior - Refined system prompt to distinguish between: * 'show/example/how does' → respond with code in chat * 'create/add/implement/fix' → use write_file tool - Removed aggressive AGENT DIRECTIVE prefix from user messages - Softened reminder message to reflect nuanced behavior - Agent can now both teach (show examples) and implement (write files) Updated Specs - Added Text Alignment and Readability section to UI_UX.md - Added Syntax Highlighting section with implementation details - Updated SDSW workflow: acceptance criteria marked complete only after user acceptance Dependencies - Added react-syntax-highlighter and @types/react-syntax-highlighter
This commit is contained in:
@@ -67,10 +67,15 @@ When the user asks for a feature, follow this 4-step loop strictly:
|
||||
### Step 4: Verification (Close)
|
||||
* **Action:** Write a test case that maps directly to the Acceptance Criteria in the Story.
|
||||
* **Action:** Run compilation and make sure it succeeds without errors. Fix warnings if possible. Run tests and make sure they all pass before proceeding. Ask questions here if needed.
|
||||
* **Action:** Ask the user to accept the story.
|
||||
* **Action:** When the user accepts, move the story file to `stories/archive/` (e.g., `mv stories/XX_story_name.md stories/archive/`).
|
||||
* **Action:** Commit the archive move to the feature branch.
|
||||
* **Action:** Tell the user to **Squash Merge** the feature branch (e.g., `git merge --squash feature/story-name`) and commit. This ensures the main history reflects one atomic commit per Story, including the archived story file.
|
||||
* **Action:** Ask the user to accept the story. **Wait for user acceptance.**
|
||||
* **Action:** When the user accepts:
|
||||
1. Mark the acceptance criteria as complete (change `[ ]` to `[x]`)
|
||||
2. Move the story file to `stories/archive/` (e.g., `mv stories/XX_story_name.md stories/archive/`)
|
||||
3. Commit both changes to the feature branch
|
||||
4. Perform the squash merge: `git merge --squash feature/story-name`
|
||||
5. Commit to master with a comprehensive commit message
|
||||
6. Delete the feature branch: `git branch -D feature/story-name`
|
||||
* **Important:** Do NOT mark acceptance criteria as complete before user acceptance. Only mark them complete when the user explicitly accepts the story.
|
||||
|
||||
|
||||
---
|
||||
|
||||
@@ -82,3 +82,78 @@ Scroll bars should be hidden while maintaining full scroll functionality.
|
||||
* 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
|
||||
|
||||
## Text Alignment and Readability
|
||||
|
||||
### Problem
|
||||
Center-aligned text in a chat interface is unconventional and reduces readability, especially for code blocks and long-form content. Standard chat UIs align messages differently based on the sender.
|
||||
|
||||
### Solution: Context-Appropriate Text Alignment
|
||||
Messages should follow standard chat UI conventions with proper alignment based on message type.
|
||||
|
||||
### Requirements
|
||||
|
||||
1. **User Messages:** Right-aligned (standard pattern showing messages sent by the user)
|
||||
2. **Assistant Messages:** Left-aligned (standard pattern showing messages received)
|
||||
3. **Tool Outputs:** Left-aligned (part of the system/assistant response flow)
|
||||
4. **Code Blocks:** Always left-aligned regardless of message type (for readability)
|
||||
5. **Container:** Remove any center-alignment from the chat container
|
||||
6. **Max-Width:** Maintain current max-width constraint (e.g., 768px) for optimal readability
|
||||
7. **Spacing:** Maintain proper padding and visual hierarchy between messages
|
||||
|
||||
### Implementation Notes
|
||||
* Check for `textAlign: "center"` in inline styles and remove
|
||||
* Check for `text-align: center` in CSS and remove from chat-related classes
|
||||
* Ensure flexbox alignment is set appropriately:
|
||||
* User messages: `alignItems: "flex-end"`
|
||||
* Assistant/Tool messages: `alignItems: "flex-start"`
|
||||
* Code blocks should have `text-align: left` explicitly set
|
||||
|
||||
## Syntax Highlighting
|
||||
|
||||
### Problem
|
||||
Code blocks in assistant responses currently lack syntax highlighting, making them harder to read and understand. Developers expect colored syntax highlighting similar to their code editors.
|
||||
|
||||
### Solution: Syntax Highlighting for Code Blocks
|
||||
Integrate syntax highlighting into markdown code blocks rendered by the assistant.
|
||||
|
||||
### Requirements
|
||||
|
||||
1. **Languages Supported:** At minimum:
|
||||
- JavaScript/TypeScript
|
||||
- Rust
|
||||
- Python
|
||||
- JSON
|
||||
- Markdown
|
||||
- Shell/Bash
|
||||
- HTML/CSS
|
||||
- SQL
|
||||
2. **Theme:** Use a dark theme that complements the existing dark UI (e.g., `oneDark`, `vsDark`, `dracula`)
|
||||
3. **Integration:** Work seamlessly with `react-markdown` component
|
||||
4. **Performance:** Should not significantly impact rendering performance
|
||||
5. **Fallback:** Plain monospace text for unrecognized languages
|
||||
6. **Inline Code:** Inline code (single backticks) should maintain simple styling without full syntax highlighting
|
||||
|
||||
### Implementation Notes
|
||||
* Use `react-syntax-highlighter` library with `react-markdown`
|
||||
* Or use `rehype-highlight` plugin for `react-markdown`
|
||||
* Configure with a dark theme preset (e.g., `oneDark` from `react-syntax-highlighter/dist/esm/styles/prism`)
|
||||
* Apply to code blocks via `react-markdown` components prop:
|
||||
```tsx
|
||||
<Markdown
|
||||
components={{
|
||||
code: ({node, inline, className, children, ...props}) => {
|
||||
const match = /language-(\w+)/.exec(className || '');
|
||||
return !inline && match ? (
|
||||
<SyntaxHighlighter style={oneDark} language={match[1]} {...props}>
|
||||
{String(children).replace(/\n$/, '')}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code className={className} {...props}>{children}</code>
|
||||
);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
```
|
||||
* Ensure syntax highlighted code blocks are left-aligned
|
||||
* Test with various code samples to ensure proper rendering
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
all text in the chat window is currently centred, which is weird especially for code. Make it more readable.
|
||||
40
.living_spec/stories/archive/11_make_text_not_centred.md
Normal file
40
.living_spec/stories/archive/11_make_text_not_centred.md
Normal file
@@ -0,0 +1,40 @@
|
||||
# Story: Left-Align Chat Text and Add Syntax Highlighting
|
||||
|
||||
## User Story
|
||||
**As a** User
|
||||
**I want** chat messages and code to be left-aligned instead of centered, with proper syntax highlighting for code blocks
|
||||
**So that** the text is more readable, follows standard chat UI conventions, and code is easier to understand.
|
||||
|
||||
## Acceptance Criteria
|
||||
* [x] User messages should be right-aligned (standard chat pattern)
|
||||
* [x] Assistant messages should be left-aligned
|
||||
* [x] Tool outputs should be left-aligned
|
||||
* [x] Code blocks and monospace text should be left-aligned
|
||||
* [x] Remove any center-alignment styling from the chat container
|
||||
* [x] Maintain the current max-width constraint for readability
|
||||
* [x] Ensure proper spacing and padding for visual hierarchy
|
||||
* [x] Add syntax highlighting for code blocks in assistant messages
|
||||
* [x] Support common languages: JavaScript, TypeScript, Rust, Python, JSON, Markdown, Shell, etc.
|
||||
* [x] Syntax highlighting should work with the dark theme
|
||||
|
||||
## Out of Scope
|
||||
* Redesigning the entire chat layout
|
||||
* Adding avatars or profile pictures
|
||||
* Changing the overall color scheme or theme (syntax highlighting colors should complement existing dark theme)
|
||||
* Custom themes for syntax highlighting
|
||||
|
||||
## Implementation Notes
|
||||
* Check `Chat.tsx` for any `textAlign: "center"` styles
|
||||
* Check `App.css` for any center-alignment rules affecting the chat
|
||||
* User messages should align to the right with appropriate styling
|
||||
* Assistant and tool messages should align to the left
|
||||
* Code blocks should always be left-aligned for readability
|
||||
* For syntax highlighting, consider using:
|
||||
* `react-syntax-highlighter` (works with react-markdown)
|
||||
* Or `prism-react-renderer` for lighter bundle size
|
||||
* Or integrate with `rehype-highlight` plugin for react-markdown
|
||||
* Use a dark theme preset like `oneDark`, `vsDark`, or `dracula`
|
||||
* Syntax highlighting should be applied to markdown code blocks automatically
|
||||
|
||||
## Related Functional Specs
|
||||
* Functional Spec: UI/UX
|
||||
5
HelloWorld.java
Normal file
5
HelloWorld.java
Normal file
@@ -0,0 +1,5 @@
|
||||
public class HelloWorld {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
}
|
||||
14
package.json
14
package.json
@@ -15,21 +15,23 @@
|
||||
"@tauri-apps/plugin-dialog": "^2.4.2",
|
||||
"@tauri-apps/plugin-opener": "^2",
|
||||
"@tauri-apps/plugin-store": "^2.4.1",
|
||||
"@types/react-syntax-highlighter": "^15.5.13",
|
||||
"react": "^19.1.0",
|
||||
"react-dom": "^19.1.0",
|
||||
"react-markdown": "^10.1.0"
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-syntax-highlighter": "^16.1.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/react": "^19.1.8",
|
||||
"@types/react-dom": "^19.1.6",
|
||||
"@vitejs/plugin-react": "^4.6.0",
|
||||
"typescript": "~5.8.3",
|
||||
"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"
|
||||
"typescript": "~5.8.3",
|
||||
"vite": "^7.0.4"
|
||||
}
|
||||
}
|
||||
|
||||
2978
pnpm-lock.yaml
generated
2978
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -53,16 +53,6 @@ pub async fn chat(
|
||||
// 3. Agent Loop
|
||||
let mut current_history = messages.clone();
|
||||
|
||||
// Prefix user messages with reminder for stubborn models
|
||||
for msg in &mut current_history {
|
||||
if msg.role == Role::User && !msg.content.starts_with("[AGENT DIRECTIVE]") {
|
||||
msg.content = format!(
|
||||
"[AGENT DIRECTIVE: You must use write_file tool to implement changes. Never suggest code.]\n\n{}",
|
||||
msg.content
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Inject System Prompt
|
||||
current_history.insert(
|
||||
0,
|
||||
@@ -74,12 +64,12 @@ pub async fn chat(
|
||||
},
|
||||
);
|
||||
|
||||
// Inject aggressive reminder as a second system message
|
||||
// Inject reminder as a second system message
|
||||
current_history.insert(
|
||||
1,
|
||||
Message {
|
||||
role: Role::System,
|
||||
content: "CRITICAL REMINDER: When the user asks you to create, modify, or implement code, you MUST call the write_file tool with the complete file content. DO NOT output code in markdown blocks. DO NOT suggest what the user should do. TAKE ACTION IMMEDIATELY using tools.".to_string(),
|
||||
content: "REMINDER: Distinguish between showing examples (use code blocks in chat) vs implementing changes (use write_file tool). Keywords like 'show me', 'example', 'how does' = chat response. Keywords like 'create', 'add', 'implement', 'fix' = use tools.".to_string(),
|
||||
tool_calls: None,
|
||||
tool_call_id: None,
|
||||
},
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
pub const SYSTEM_PROMPT: &str = r#"You are an AI Agent with direct access to the user's filesystem and development environment.
|
||||
|
||||
CRITICAL INSTRUCTIONS:
|
||||
1. **YOU ARE NOT A CHATBOT.** You do not suggest code or provide instructions for the user to follow.
|
||||
2. **YOU WRITE CODE DIRECTLY.** When the user asks you to create, modify, or fix code, you MUST use the `write_file` tool to write the actual files.
|
||||
3. **DO NOT OUTPUT CODE BLOCKS.** Do not write code in markdown code blocks (```) for the user to copy. That is forbidden. Use tools instead.
|
||||
1. **Distinguish Between Examples and Implementation:**
|
||||
- If the user asks to "show", "give me an example", "how would I", or "what does X look like" → Respond with code in the chat
|
||||
- If the user asks to "create", "add", "implement", "write", "fix", "modify", or "update" → Use `write_file` tool
|
||||
2. **When Implementing:** Use the `write_file` tool to write actual files to disk
|
||||
3. **When Teaching/Showing:** You CAN use markdown code blocks to demonstrate examples or explain concepts
|
||||
4. **Context Matters:** If discussing a file that exists in the project, use tools. If showing generic examples, use code blocks.
|
||||
|
||||
YOUR CAPABILITIES:
|
||||
You have the following tools available:
|
||||
@@ -29,47 +32,60 @@ CRITICAL RULES:
|
||||
|
||||
EXAMPLES OF CORRECT BEHAVIOR:
|
||||
|
||||
Example 1 - User asks to add a feature:
|
||||
Example 1 - User asks for an EXAMPLE (show in chat):
|
||||
User: "Show me a Java hello world"
|
||||
You (correct): "Here's a simple Java hello world program:
|
||||
```java
|
||||
public class HelloWorld {
|
||||
public static void main(String[] args) {
|
||||
System.out.println("Hello, World!");
|
||||
}
|
||||
}
|
||||
```"
|
||||
|
||||
Example 2 - User asks to IMPLEMENT (use tools):
|
||||
User: "Add error handling to the login function in auth.rs"
|
||||
You (correct): [Call read_file("src/auth.rs"), analyze it, then call write_file("src/auth.rs", <complete file with error handling>), then call exec_shell("cargo", ["check"])]
|
||||
You (correct response): "I've added error handling to the login function using Result<T, E> and added proper error propagation. The code compiles successfully."
|
||||
|
||||
Example 2 - User asks to create a new file:
|
||||
Example 3 - User asks to CREATE (use tools):
|
||||
User: "Create a new component called Button.tsx in the components folder"
|
||||
You (correct): [Call read_file("src/components/SomeExisting.tsx") to understand the project's component style, then call write_file("src/components/Button.tsx", <complete component code>)]
|
||||
You (correct response): "I've created Button.tsx with TypeScript interfaces and following the existing component patterns in your project."
|
||||
|
||||
Example 3 - User asks to fix a bug:
|
||||
Example 4 - User asks to FIX (use tools):
|
||||
User: "The calculation in utils.js is wrong"
|
||||
You (correct): [Call read_file("src/utils.js"), identify the bug, call write_file("src/utils.js", <complete corrected file>), call exec_shell("npm", ["test"])]
|
||||
You (correct response): "I've fixed the calculation error in utils.js. The formula now correctly handles edge cases and all tests pass."
|
||||
|
||||
EXAMPLES OF INCORRECT BEHAVIOR (DO NOT DO THIS):
|
||||
|
||||
Example 1 - Suggesting code instead of writing it:
|
||||
User: "Add error handling to the login function"
|
||||
You (WRONG): "Here's how you can add error handling:
|
||||
```rust
|
||||
fn login() -> Result<User, LoginError> {
|
||||
// your code here
|
||||
}
|
||||
```
|
||||
Add this to your auth.rs file."
|
||||
Example 1 - Writing a file when user asks for an example:
|
||||
User: "Show me a React component"
|
||||
You (WRONG): [Calls write_file("Component.tsx", ...)]
|
||||
You (CORRECT): Show the code in a markdown code block in the chat
|
||||
|
||||
Example 2 - Writing partial code:
|
||||
Example 2 - Suggesting code when user asks to implement:
|
||||
User: "Add error handling to the login function"
|
||||
You (WRONG): "Here's how you can add error handling: ```rust fn login() -> Result<User, LoginError> { ... } ``` Add this to your auth.rs file."
|
||||
You (CORRECT): [Use read_file then write_file to actually implement it]
|
||||
|
||||
Example 3 - Writing partial code:
|
||||
User: "Update the API endpoint"
|
||||
You (WRONG): [Calls write_file with content like "// ... existing imports\n\nfn new_endpoint() { }\n\n// ... rest of file"]
|
||||
You (CORRECT): Read the file first, then write the COMPLETE file with all content
|
||||
|
||||
Example 3 - Asking for information you can discover:
|
||||
Example 4 - Asking for information you can discover:
|
||||
User: "Add a new route to the app"
|
||||
You (WRONG): "What file contains your routes?"
|
||||
You (CORRECT): [Call search_files("route") or list_directory("src") to find the routing file yourself]
|
||||
|
||||
REMEMBER:
|
||||
- You have the power to read and write files directly
|
||||
- The user expects you to IMPLEMENT changes, not describe them
|
||||
- Always use write_file with complete file contents
|
||||
- Verify your work with exec_shell when appropriate
|
||||
- **Teaching vs Implementing:** Show examples in chat, implement changes with tools
|
||||
- **Keywords matter:** "show/example" = chat, "create/add/fix" = tools
|
||||
- **Complete files:** Always write the COMPLETE file content when using write_file
|
||||
- **Verify your work:** Use exec_shell to run tests/checks after implementing changes
|
||||
- You have the power to both teach AND implement - use the right mode for the situation
|
||||
|
||||
Remember: You are an autonomous agent. Act, don't advise.
|
||||
Remember: You are an autonomous agent that can both explain concepts and take action. Choose appropriately based on the user's request.
|
||||
"#;
|
||||
|
||||
17
src/App.css
17
src/App.css
@@ -27,7 +27,6 @@
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.logo {
|
||||
@@ -138,6 +137,7 @@ details summary span:first-child {
|
||||
/* Markdown body styling for dark theme */
|
||||
.markdown-body {
|
||||
color: #ececec;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown-body code {
|
||||
@@ -152,6 +152,7 @@ details summary span:first-child {
|
||||
padding: 12px;
|
||||
border-radius: 6px;
|
||||
overflow-x: auto;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown-body pre code {
|
||||
@@ -159,6 +160,20 @@ details summary span:first-child {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Syntax highlighter styling */
|
||||
.markdown-body div[class*="language-"] {
|
||||
margin: 0;
|
||||
border-radius: 6px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown-body pre[class*="language-"] {
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
background: #1a1a1a;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* Hide scroll bars globally while maintaining scroll functionality */
|
||||
/* Firefox */
|
||||
* {
|
||||
|
||||
@@ -2,6 +2,8 @@ import { useState, useRef, useEffect } from "react";
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import Markdown from "react-markdown";
|
||||
import { Prism as SyntaxHighlighter } from "react-syntax-highlighter";
|
||||
import { oneDark } from "react-syntax-highlighter/dist/esm/styles/prism";
|
||||
import { Message, ProviderConfig } from "../types";
|
||||
|
||||
interface ChatProps {
|
||||
@@ -335,8 +337,29 @@ export function Chat({ projectPath, onCloseProject }: ChatProps) {
|
||||
</details>
|
||||
) : (
|
||||
<div className="markdown-body">
|
||||
{/* Assuming global CSS handles standard markdown styling now */}
|
||||
<Markdown>{msg.content}</Markdown>
|
||||
<Markdown
|
||||
components={{
|
||||
code: ({ className, children, ...props }: any) => {
|
||||
const match = /language-(\w+)/.exec(className || "");
|
||||
const isInline = !className;
|
||||
return !isInline && match ? (
|
||||
<SyntaxHighlighter
|
||||
style={oneDark as any}
|
||||
language={match[1]}
|
||||
PreTag="div"
|
||||
>
|
||||
{String(children).replace(/\n$/, "")}
|
||||
</SyntaxHighlighter>
|
||||
) : (
|
||||
<code className={className} {...props}>
|
||||
{children}
|
||||
</code>
|
||||
);
|
||||
},
|
||||
}}
|
||||
>
|
||||
{msg.content}
|
||||
</Markdown>
|
||||
</div>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user