diff --git a/.claude/agents/coder.md b/.claude/agents/coder.md new file mode 100644 index 0000000..d09f907 --- /dev/null +++ b/.claude/agents/coder.md @@ -0,0 +1,132 @@ +--- +name: coder +description: "Use this agent when you need to implement new features, write new code, refactor existing code, or make any code changes to the codebase. This agent should be invoked for tasks requiring high-quality, production-ready code implementation.\\n\\nExamples:\\n\\n\\nContext: User requests a new feature implementation\\nuser: \"Add a function to validate email addresses\"\\nassistant: \"I'll use the coder agent to implement a high-quality email validation function that follows the project's patterns and best practices.\"\\n\\n\\n\\n\\nContext: User needs a new API endpoint\\nuser: \"Create a REST endpoint for user authentication\"\\nassistant: \"Let me invoke the coder agent to implement this authentication endpoint with proper security practices and project standards.\"\\n\\n\\n\\n\\nContext: User asks for a React component\\nuser: \"Build a data table component with sorting and filtering\"\\nassistant: \"I'll launch the coder agent to create this component following the project's neobrutalism design system and established React patterns.\"\\n\\n\\n\\n\\nContext: User requests code refactoring\\nuser: \"Refactor the database module to use connection pooling\"\\nassistant: \"I'll use the coder agent to carefully refactor this module while maintaining all existing functionality and improving performance.\"\\n\\n" +model: opus +color: orange +--- + +You are an elite software architect and principal engineer with over 20 years of experience across diverse technology stacks. You have contributed to major open-source projects, led engineering teams at top-tier tech companies, and have deep expertise in building scalable, maintainable, and secure software systems. + +## Your Core Identity + +You are meticulous, thorough, and uncompromising in code quality. You never take shortcuts. You treat every line of code as if it will be maintained for decades. You believe that code is read far more often than it is written, and you optimize for clarity and maintainability above all else. + +## Mandatory Workflow + +### Phase 1: Research and Understanding + +Before writing ANY code, you MUST: + +1. **Explore the Codebase**: Use file reading tools to understand the project structure, existing patterns, and architectural decisions. Look for: + - Directory structure and module organization + - Existing similar implementations to use as reference + - Configuration files (package.json, pyproject.toml, tsconfig.json, etc.) + - README files and documentation + - CLAUDE.md or similar project instruction files + +2. **Identify Patterns and Standards**: Search for and document: + - Naming conventions (files, functions, classes, variables) + - Code organization patterns (how similar code is structured) + - Error handling approaches + - Logging conventions + - Testing patterns + - Import/export styles + - Comment and documentation styles + +3. **Research External Dependencies**: When implementing features using frameworks or libraries: + - Use web search to find the latest documentation and best practices + - Use web fetch to retrieve official documentation pages + - Look for migration guides if the project uses older versions + - Identify security advisories or known issues + - Find recommended patterns from the library authors + +### Phase 2: Implementation + +When writing code, you MUST adhere to these principles: + +**Code Quality Standards:** +- Write self-documenting code with clear, descriptive names +- Add comments that explain WHY, not WHAT (the code shows what) +- Keep functions small and focused on a single responsibility +- Use meaningful variable names that reveal intent +- Avoid magic numbers and strings - use named constants +- Handle all error cases explicitly +- Validate inputs at system boundaries +- Use defensive programming techniques + +**Security Requirements:** +- Never hardcode secrets, credentials, or API keys +- Sanitize and validate all user inputs +- Use parameterized queries for database operations +- Follow the principle of least privilege +- Implement proper authentication and authorization checks +- Be aware of common vulnerabilities (XSS, CSRF, injection attacks) + +**Performance Considerations:** +- Consider time and space complexity +- Avoid premature optimization but don't ignore obvious inefficiencies +- Use appropriate data structures for the task +- Be mindful of database query efficiency +- Consider caching where appropriate + +**Modularity and Maintainability:** +- Follow the Single Responsibility Principle +- Create clear interfaces between components +- Minimize dependencies between modules +- Make code testable by design +- Prefer composition over inheritance +- Keep files focused and reasonably sized + +**Code Style Consistency:** +- Match the existing codebase style exactly +- Follow the established indentation and formatting +- Use consistent quote styles, semicolons, and spacing +- Organize imports according to project conventions +- Follow the project's file and folder naming patterns + +### Phase 3: Verification + +After implementing code, you MUST run all available verification commands: + +1. **Linting**: Run the project's linter (eslint, pylint, ruff, etc.) +2. **Type Checking**: Run type checkers (typescript, mypy, pyright, etc.) +3. **Formatting**: Ensure code is properly formatted (prettier, black, etc.) +4. **Tests**: Run relevant tests if they exist + +Fix ALL issues before considering the implementation complete. Never leave linting errors, type errors, or failing tests. + +## Project-Specific Context + +For this project (autocoder): +- **Python Backend**: Uses SQLAlchemy, FastAPI, follows patterns in `api/`, `mcp_server/` +- **React UI**: Uses React 18, TypeScript, TanStack Query, Tailwind CSS v4, Radix UI +- **Design System**: Neobrutalism style with specific color tokens and animations +- **Security**: Defense-in-depth with bash command allowlists +- **MCP Pattern**: Feature management through MCP server tools + +Always check: +- `requirements.txt` for Python dependencies +- `ui/package.json` for React dependencies +- `ui/src/styles/globals.css` for design tokens +- `security.py` for allowed commands +- Existing components in `ui/src/components/` for UI patterns +- Existing routers in `server/routers/` for API patterns + +## Communication Style + +- Explain your reasoning and decisions +- Document what patterns you found and are following +- Note any concerns or tradeoffs you considered +- Be explicit about what verification steps you ran and their results +- If you encounter issues, explain how you resolved them + +## Non-Negotiable Rules + +1. NEVER skip the research phase - always understand before implementing +2. NEVER leave code that doesn't pass lint and type checks +3. NEVER introduce code that doesn't match existing patterns without explicit justification +4. NEVER ignore error cases or edge conditions +5. NEVER write code without comments explaining complex logic +6. ALWAYS verify your implementation compiles and passes checks before finishing +7. ALWAYS use web search and fetch to get up-to-date information about libraries +8. ALWAYS explore the codebase first to understand existing patterns diff --git a/.claude/templates/coding_prompt.template.md b/.claude/templates/coding_prompt.template.md index 6da10a2..823d297 100644 --- a/.claude/templates/coding_prompt.template.md +++ b/.claude/templates/coding_prompt.template.md @@ -17,8 +17,8 @@ ls -la # 3. Read the project specification to understand what you're building cat app_spec.txt -# 4. Read progress notes from previous sessions -cat claude-progress.txt +# 4. Read progress notes from previous sessions (last 500 lines to avoid context overflow) +tail -500 claude-progress.txt # 5. Check recent git history git log --oneline -20 diff --git a/.claude/templates/coding_prompt_yolo.template.md b/.claude/templates/coding_prompt_yolo.template.md index 5e2f1b7..1ab2179 100644 --- a/.claude/templates/coding_prompt_yolo.template.md +++ b/.claude/templates/coding_prompt_yolo.template.md @@ -28,8 +28,8 @@ ls -la # 3. Read the project specification to understand what you're building cat app_spec.txt -# 4. Read progress notes from previous sessions -cat claude-progress.txt +# 4. Read progress notes from previous sessions (last 500 lines to avoid context overflow) +tail -500 claude-progress.txt # 5. Check recent git history git log --oneline -20 diff --git a/.env.example b/.env.example index fe59407..157af45 100644 --- a/.env.example +++ b/.env.example @@ -1,2 +1,16 @@ # Optional: N8N webhook for progress notifications # PROGRESS_N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/... + +# CLI Command Selection +# Choose which CLI command to use for the agent. +# - claude: Uses Anthropic's official Claude Code CLI (default) +# - glm: Uses GLM CLI (or any other compatible CLI) +# Defaults to 'claude' if not specified +# CLI_COMMAND=claude + +# Playwright Browser Mode +# Controls whether Playwright runs Chrome in headless mode (no visible browser window). +# - true: Browser runs in background, invisible (recommended for using PC while agent works) +# - false: Browser opens a visible window (useful for debugging) +# Defaults to 'false' if not specified +# PLAYWRIGHT_HEADLESS=false diff --git a/.gitignore b/.gitignore index d8f6ab0..dccad2d 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ generations/ nul +issues/ # Log files logs/ diff --git a/client.py b/client.py index 72d5b92..c058276 100644 --- a/client.py +++ b/client.py @@ -13,9 +13,45 @@ from pathlib import Path from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient from claude_agent_sdk.types import HookMatcher +from dotenv import load_dotenv from security import bash_security_hook +# Load environment variables from .env file if present +load_dotenv() + +# Default CLI command - can be overridden via CLI_COMMAND environment variable +# Common values: "claude" (default), "glm" +DEFAULT_CLI_COMMAND = "claude" + +# Default Playwright headless mode - can be overridden via PLAYWRIGHT_HEADLESS env var +# When True, browser runs invisibly in background +# When False, browser window is visible (default - useful for monitoring agent progress) +DEFAULT_PLAYWRIGHT_HEADLESS = False + + +def get_cli_command() -> str: + """ + Get the CLI command to use for the agent. + + Reads from CLI_COMMAND environment variable, defaults to 'claude'. + This allows users to use alternative CLIs like 'glm'. + """ + return os.getenv("CLI_COMMAND", DEFAULT_CLI_COMMAND) + + +def get_playwright_headless() -> bool: + """ + Get the Playwright headless mode setting. + + Reads from PLAYWRIGHT_HEADLESS environment variable, defaults to False. + Returns True for headless mode (invisible browser), False for visible browser. + """ + value = os.getenv("PLAYWRIGHT_HEADLESS", "false").lower() + # Accept various truthy/falsy values + return value in ("true", "1", "yes", "on") + + # Feature MCP tools for feature/test management FEATURE_MCP_TOOLS = [ "mcp__features__feature_get_stats", @@ -151,12 +187,14 @@ def create_client(project_dir: Path, model: str, yolo_mode: bool = False): print(" - Project settings enabled (skills, commands, CLAUDE.md)") print() - # Use system Claude CLI instead of bundled one (avoids Bun runtime crash on Windows) - system_cli = shutil.which("claude") + # Use system CLI instead of bundled one (avoids Bun runtime crash on Windows) + # CLI command is configurable via CLI_COMMAND environment variable + cli_command = get_cli_command() + system_cli = shutil.which(cli_command) if system_cli: print(f" - Using system CLI: {system_cli}") else: - print(" - Warning: System Claude CLI not found, using bundled CLI") + print(f" - Warning: System CLI '{cli_command}' not found, using bundled CLI") # Build MCP servers config - features is always included, playwright only in standard mode mcp_servers = { @@ -174,9 +212,13 @@ def create_client(project_dir: Path, model: str, yolo_mode: bool = False): } if not yolo_mode: # Include Playwright MCP server for browser automation (standard mode only) + # Headless mode is configurable via PLAYWRIGHT_HEADLESS environment variable + playwright_args = ["@playwright/mcp@latest", "--viewport-size", "1280x720"] + if get_playwright_headless(): + playwright_args.append("--headless") mcp_servers["playwright"] = { "command": "npx", - "args": ["@playwright/mcp@latest", "--viewport-size", "1280x720"], + "args": playwright_args, } return ClaudeSDKClient( diff --git a/requirements.txt b/requirements.txt index a12673d..1ff89a7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -claude-agent-sdk>=0.1.0 +claude-agent-sdk>=0.1.0,<0.2.0 python-dotenv>=1.0.0 sqlalchemy>=2.0.0 fastapi>=0.115.0 diff --git a/server/main.py b/server/main.py index 586103b..91b9875 100644 --- a/server/main.py +++ b/server/main.py @@ -6,10 +6,26 @@ Main entry point for the Autonomous Coding UI server. Provides REST API, WebSocket, and static file serving. """ +import os import shutil from contextlib import asynccontextmanager from pathlib import Path +from dotenv import load_dotenv + +# Load environment variables from .env file if present +load_dotenv() + + +def get_cli_command() -> str: + """ + Get the CLI command to use for the agent. + + Reads from CLI_COMMAND environment variable, defaults to 'claude'. + This allows users to use alternative CLIs like 'glm'. + """ + return os.getenv("CLI_COMMAND", "claude") + from fastapi import FastAPI, HTTPException, Request, WebSocket from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse @@ -124,11 +140,12 @@ async def health_check(): @app.get("/api/setup/status", response_model=SetupStatus) async def setup_status(): """Check system setup status.""" - # Check for Claude CLI - claude_cli = shutil.which("claude") is not None + # Check for CLI (configurable via CLI_COMMAND environment variable) + cli_command = get_cli_command() + claude_cli = shutil.which(cli_command) is not None - # Check for Claude CLI configuration directory - # Note: Claude CLI no longer stores credentials in ~/.claude/.credentials.json + # Check for CLI configuration directory + # Note: CLI no longer stores credentials in ~/.claude/.credentials.json # The existence of ~/.claude indicates the CLI has been configured claude_dir = Path.home() / ".claude" credentials = claude_dir.exists() and claude_dir.is_dir() diff --git a/server/services/assistant_chat_session.py b/server/services/assistant_chat_session.py index c6c6c1a..bebed94 100755 --- a/server/services/assistant_chat_session.py +++ b/server/services/assistant_chat_session.py @@ -18,12 +18,27 @@ from pathlib import Path from typing import AsyncGenerator, Optional from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient +from dotenv import load_dotenv from .assistant_database import ( add_message, create_conversation, ) +# Load environment variables from .env file if present +load_dotenv() + + +def get_cli_command() -> str: + """ + Get the CLI command to use for the agent. + + Reads from CLI_COMMAND environment variable, defaults to 'claude'. + This allows users to use alternative CLIs like 'glm'. + """ + return os.getenv("CLI_COMMAND", "claude") + + logger = logging.getLogger(__name__) # Root directory of the project @@ -227,8 +242,9 @@ class AssistantChatSession: # Get system prompt with project context system_prompt = get_system_prompt(self.project_name, self.project_dir) - # Use system Claude CLI - system_cli = shutil.which("claude") + # Use system CLI (configurable via CLI_COMMAND environment variable) + cli_command = get_cli_command() + system_cli = shutil.which(cli_command) try: self.client = ClaudeSDKClient( diff --git a/server/services/expand_chat_session.py b/server/services/expand_chat_session.py index 6c6b430..659c776 100644 --- a/server/services/expand_chat_session.py +++ b/server/services/expand_chat_session.py @@ -9,6 +9,7 @@ Uses the expand-project.md skill to help users add features to existing projects import asyncio import json import logging +import os import re import shutil import threading @@ -18,9 +19,23 @@ from pathlib import Path from typing import AsyncGenerator, Optional from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient +from dotenv import load_dotenv from ..schemas import ImageAttachment +# Load environment variables from .env file if present +load_dotenv() + + +def get_cli_command() -> str: + """ + Get the CLI command to use for the agent. + + Reads from CLI_COMMAND environment variable, defaults to 'claude'. + This allows users to use alternative CLIs like 'glm'. + """ + return os.getenv("CLI_COMMAND", "claude") + logger = logging.getLogger(__name__) @@ -120,12 +135,14 @@ class ExpandChatSession: except UnicodeDecodeError: skill_content = skill_path.read_text(encoding="utf-8", errors="replace") - # Find and validate Claude CLI before creating temp files - system_cli = shutil.which("claude") + # Find and validate CLI before creating temp files + # CLI command is configurable via CLI_COMMAND environment variable + cli_command = get_cli_command() + system_cli = shutil.which(cli_command) if not system_cli: yield { "type": "error", - "content": "Claude CLI not found. Please install Claude Code." + "content": f"CLI '{cli_command}' not found. Please install it or check your CLI_COMMAND setting." } return diff --git a/server/services/spec_chat_session.py b/server/services/spec_chat_session.py index 7cec9fb..7cb2beb 100644 --- a/server/services/spec_chat_session.py +++ b/server/services/spec_chat_session.py @@ -8,6 +8,7 @@ Uses the create-spec.md skill to guide users through app spec creation. import json import logging +import os import shutil import threading from datetime import datetime @@ -15,9 +16,23 @@ from pathlib import Path from typing import AsyncGenerator, Optional from claude_agent_sdk import ClaudeAgentOptions, ClaudeSDKClient +from dotenv import load_dotenv from ..schemas import ImageAttachment +# Load environment variables from .env file if present +load_dotenv() + + +def get_cli_command() -> str: + """ + Get the CLI command to use for the agent. + + Reads from CLI_COMMAND environment variable, defaults to 'claude'. + This allows users to use alternative CLIs like 'glm'. + """ + return os.getenv("CLI_COMMAND", "claude") + logger = logging.getLogger(__name__) @@ -142,7 +157,9 @@ class SpecChatSession: # Create Claude SDK client with limited tools for spec creation # Use Opus for best quality spec generation # Use system CLI to avoid bundled Bun runtime crash (exit code 3) on Windows - system_cli = shutil.which("claude") + # CLI command is configurable via CLI_COMMAND environment variable + cli_command = get_cli_command() + system_cli = shutil.which(cli_command) try: self.client = ClaudeSDKClient( options=ClaudeAgentOptions( diff --git a/start.py b/start.py index 5084d96..df97909 100644 --- a/start.py +++ b/start.py @@ -13,7 +13,24 @@ import subprocess import sys from pathlib import Path +from dotenv import load_dotenv + from auth import is_auth_error, print_auth_error_help + +# Load environment variables from .env file if present +load_dotenv() + + +def get_cli_command() -> str: + """ + Get the CLI command to use for the agent. + + Reads from CLI_COMMAND environment variable, defaults to 'claude'. + This allows users to use alternative CLIs like 'glm'. + """ + return os.getenv("CLI_COMMAND", "claude") + + from prompts import ( get_project_prompts_dir, has_project_prompts, @@ -217,11 +234,12 @@ def run_spec_creation(project_dir: Path) -> bool: print("Exit Claude Code (Ctrl+C or /exit) when finished.\n") try: - # Launch Claude Code with /create-spec command + # Launch CLI with /create-spec command # Project path included in command string so it populates $ARGUMENTS # Capture stderr to detect auth errors while letting stdout flow to terminal + cli_command = get_cli_command() result = subprocess.run( - ["claude", f"/create-spec {project_dir}"], + [cli_command, f"/create-spec {project_dir}"], check=False, # Don't raise on non-zero exit cwd=str(Path(__file__).parent), # Run from project root stderr=subprocess.PIPE, @@ -249,13 +267,17 @@ def run_spec_creation(project_dir: Path) -> bool: print(f"Please ensure app_spec.txt exists in: {get_project_prompts_dir(project_dir)}") # If failed with non-zero exit and no spec, might be auth issue if result.returncode != 0: - print("\nIf you're having authentication issues, try running: claude login") + print(f"\nIf you're having authentication issues, try running: {cli_command} login") return False except FileNotFoundError: - print("\nError: 'claude' command not found.") - print("Make sure Claude Code CLI is installed:") - print(" npm install -g @anthropic-ai/claude-code") + cli_command = get_cli_command() + print(f"\nError: '{cli_command}' command not found.") + if cli_command == "claude": + print("Make sure Claude Code CLI is installed:") + print(" npm install -g @anthropic-ai/claude-code") + else: + print(f"Make sure the '{cli_command}' CLI is installed and in your PATH.") return False except KeyboardInterrupt: print("\n\nSpec creation cancelled.") @@ -407,7 +429,8 @@ def run_agent(project_name: str, project_dir: Path) -> None: print(f"\nAgent error:\n{stderr_output.strip()}") # Still hint about auth if exit was unexpected if "error" in stderr_output.lower() or "exception" in stderr_output.lower(): - print("\nIf this is an authentication issue, try running: claude login") + cli_command = get_cli_command() + print(f"\nIf this is an authentication issue, try running: {cli_command} login") except KeyboardInterrupt: print("\n\nAgent interrupted. Run again to resume.") diff --git a/ui/src/components/ConfirmDialog.tsx b/ui/src/components/ConfirmDialog.tsx new file mode 100644 index 0000000..191571f --- /dev/null +++ b/ui/src/components/ConfirmDialog.tsx @@ -0,0 +1,103 @@ +/** + * ConfirmDialog Component + * + * A reusable confirmation dialog following the neobrutalism design system. + * Used to confirm destructive actions like deleting projects. + */ + +import { AlertTriangle, X } from 'lucide-react' + +interface ConfirmDialogProps { + isOpen: boolean + title: string + message: string + confirmLabel?: string + cancelLabel?: string + variant?: 'danger' | 'warning' + isLoading?: boolean + onConfirm: () => void + onCancel: () => void +} + +export function ConfirmDialog({ + isOpen, + title, + message, + confirmLabel = 'Confirm', + cancelLabel = 'Cancel', + variant = 'danger', + isLoading = false, + onConfirm, + onCancel, +}: ConfirmDialogProps) { + if (!isOpen) return null + + const variantColors = { + danger: { + icon: 'var(--color-neo-danger)', + button: 'neo-btn-danger', + }, + warning: { + icon: 'var(--color-neo-pending)', + button: 'neo-btn-warning', + }, + } + + const colors = variantColors[variant] + + return ( +
+
e.stopPropagation()} + > + {/* Header */} +
+
+
+ +
+

+ {title} +

+
+ +
+ + {/* Content */} +
+

+ {message} +

+ + {/* Actions */} +
+ + +
+
+
+
+ ) +} diff --git a/ui/src/components/ProjectSelector.tsx b/ui/src/components/ProjectSelector.tsx index 03e620b..8525a63 100644 --- a/ui/src/components/ProjectSelector.tsx +++ b/ui/src/components/ProjectSelector.tsx @@ -1,7 +1,9 @@ import { useState } from 'react' -import { ChevronDown, Plus, FolderOpen, Loader2 } from 'lucide-react' +import { ChevronDown, Plus, FolderOpen, Loader2, Trash2 } from 'lucide-react' import type { ProjectSummary } from '../lib/types' import { NewProjectModal } from './NewProjectModal' +import { ConfirmDialog } from './ConfirmDialog' +import { useDeleteProject } from '../hooks/useProjects' interface ProjectSelectorProps { projects: ProjectSummary[] @@ -18,12 +20,42 @@ export function ProjectSelector({ }: ProjectSelectorProps) { const [isOpen, setIsOpen] = useState(false) const [showNewProjectModal, setShowNewProjectModal] = useState(false) + const [projectToDelete, setProjectToDelete] = useState(null) + + const deleteProject = useDeleteProject() const handleProjectCreated = (projectName: string) => { onSelectProject(projectName) setIsOpen(false) } + const handleDeleteClick = (e: React.MouseEvent, projectName: string) => { + // Prevent the click from selecting the project + e.stopPropagation() + setProjectToDelete(projectName) + } + + const handleConfirmDelete = async () => { + if (!projectToDelete) return + + try { + await deleteProject.mutateAsync(projectToDelete) + // If the deleted project was selected, clear the selection + if (selectedProject === projectToDelete) { + onSelectProject(null) + } + setProjectToDelete(null) + } catch (error) { + // Error is handled by the mutation, just close the dialog + console.error('Failed to delete project:', error) + setProjectToDelete(null) + } + } + + const handleCancelDelete = () => { + setProjectToDelete(null) + } + const selectedProjectData = projects.find(p => p.name === selectedProject) return ( @@ -70,28 +102,39 @@ export function ProjectSelector({ {projects.length > 0 ? (
{projects.map(project => ( - + {project.stats.total > 0 && ( + + {project.stats.passing}/{project.stats.total} + + )} + + +
))} ) : ( @@ -124,6 +167,19 @@ export function ProjectSelector({ onClose={() => setShowNewProjectModal(false)} onProjectCreated={handleProjectCreated} /> + + {/* Delete Confirmation Dialog */} + ) } diff --git a/ui/src/components/SpecCreationChat.tsx b/ui/src/components/SpecCreationChat.tsx index acfb24d..ee14ee2 100644 --- a/ui/src/components/SpecCreationChat.tsx +++ b/ui/src/components/SpecCreationChat.tsx @@ -43,7 +43,7 @@ export function SpecCreationChat({ const [yoloEnabled, setYoloEnabled] = useState(false) const [pendingAttachments, setPendingAttachments] = useState([]) const messagesEndRef = useRef(null) - const inputRef = useRef(null) + const inputRef = useRef(null) const fileInputRef = useRef(null) const { @@ -98,6 +98,10 @@ export function SpecCreationChat({ sendMessage(trimmed, pendingAttachments.length > 0 ? pendingAttachments : undefined) setInput('') setPendingAttachments([]) // Clear attachments after sending + // Reset textarea height after sending + if (inputRef.current) { + inputRef.current.style.height = 'auto' + } } const handleKeyDown = (e: React.KeyboardEvent) => { @@ -355,11 +359,15 @@ export function SpecCreationChat({ - setInput(e.target.value)} + onChange={(e) => { + setInput(e.target.value) + // Auto-resize the textarea + e.target.style.height = 'auto' + e.target.style.height = `${Math.min(e.target.scrollHeight, 200)}px` + }} onKeyDown={handleKeyDown} placeholder={ currentQuestions @@ -368,8 +376,9 @@ export function SpecCreationChat({ ? 'Add a message with your image(s)...' : 'Type your response... (or /exit to go to project)' } - className="neo-input flex-1" + className="neo-input flex-1 resize-none min-h-[46px] max-h-[200px] overflow-y-auto" disabled={(isLoading && !currentQuestions) || connectionStatus !== 'connected'} + rows={1} />