feat: add configurable CLI command and UI improvements

Add support for alternative CLI commands via CLI_COMMAND environment
variable, allowing users to use CLIs other than 'claude' (e.g., 'glm').
This change affects all server services and the main CLI launcher.

Key changes:

- Configurable CLI command via CLI_COMMAND env var (defaults to 'claude')
- Configurable Playwright headless mode via PLAYWRIGHT_HEADLESS env var
- Pin claude-agent-sdk version to <0.2.0 for stability
- Use tail -500 for progress notes to avoid context overflow
- Add project delete functionality with confirmation dialog
- Replace single-line input with resizable textarea in spec chat
- Add coder agent configuration for code implementation tasks
- Ignore issues/ directory in git

Files modified:
- client.py: CLI command and Playwright headless configuration
- server/main.py, server/services/*: CLI command configuration
- start.py: CLI command configuration and error messages
- .env.example: Document new environment variables
- .gitignore: Ignore issues/ directory
- requirements.txt: Pin SDK version
- .claude/templates/*: Use tail -500 for progress notes
- ui/src/components/ProjectSelector.tsx: Add delete button
- ui/src/components/SpecCreationChat.tsx: Auto-resizing textarea
- ui/src/components/ConfirmDialog.tsx: New reusable dialog
- .claude/agents/coder.md: New coder agent configuration

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-01-10 13:19:49 +02:00
parent a0f7e72361
commit 117ca89f08
16 changed files with 496 additions and 49 deletions

132
.claude/agents/coder.md Normal file
View File

@@ -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<example>\\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<Task tool invocation to launch coder agent>\\n</example>\\n\\n<example>\\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<Task tool invocation to launch coder agent>\\n</example>\\n\\n<example>\\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<Task tool invocation to launch coder agent>\\n</example>\\n\\n<example>\\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<Task tool invocation to launch coder agent>\\n</example>"
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

View File

@@ -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

View File

@@ -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

View File

@@ -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

1
.gitignore vendored
View File

@@ -2,6 +2,7 @@
generations/
nul
issues/
# Log files
logs/

View File

@@ -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(

View File

@@ -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

View File

@@ -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()

View File

@@ -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(

View File

@@ -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

View File

@@ -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(

View File

@@ -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.")

View File

@@ -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 (
<div className="neo-modal-backdrop" onClick={onCancel}>
<div
className="neo-modal w-full max-w-md"
onClick={(e) => e.stopPropagation()}
>
{/* Header */}
<div className="flex items-center justify-between p-4 border-b-3 border-[var(--color-neo-border)]">
<div className="flex items-center gap-3">
<div
className="p-2 border-2 border-[var(--color-neo-border)] shadow-[2px_2px_0px_rgba(0,0,0,1)]"
style={{ backgroundColor: colors.icon }}
>
<AlertTriangle size={20} className="text-white" />
</div>
<h2 className="font-display font-bold text-lg text-[#1a1a1a]">
{title}
</h2>
</div>
<button
onClick={onCancel}
className="neo-btn neo-btn-ghost p-2"
disabled={isLoading}
>
<X size={20} />
</button>
</div>
{/* Content */}
<div className="p-6">
<p className="text-[var(--color-neo-text-secondary)] mb-6">
{message}
</p>
{/* Actions */}
<div className="flex justify-end gap-3">
<button
onClick={onCancel}
className="neo-btn"
disabled={isLoading}
>
{cancelLabel}
</button>
<button
onClick={onConfirm}
className={`neo-btn ${colors.button}`}
disabled={isLoading}
>
{isLoading ? 'Deleting...' : confirmLabel}
</button>
</div>
</div>
</div>
</div>
)
}

View File

@@ -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<string | null>(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 ? (
<div className="max-h-[300px] overflow-auto">
{projects.map(project => (
<button
<div
key={project.name}
onClick={() => {
onSelectProject(project.name)
setIsOpen(false)
}}
className={`w-full neo-dropdown-item flex items-center justify-between ${
className={`flex items-center ${
project.name === selectedProject
? 'bg-[var(--color-neo-pending)]'
: ''
}`}
>
<span className="flex items-center gap-2">
<FolderOpen size={16} />
{project.name}
</span>
{project.stats.total > 0 && (
<span className="text-sm font-mono">
{project.stats.passing}/{project.stats.total}
<button
onClick={() => {
onSelectProject(project.name)
setIsOpen(false)
}}
className="flex-1 neo-dropdown-item flex items-center justify-between"
>
<span className="flex items-center gap-2">
<FolderOpen size={16} />
{project.name}
</span>
)}
</button>
{project.stats.total > 0 && (
<span className="text-sm font-mono">
{project.stats.passing}/{project.stats.total}
</span>
)}
</button>
<button
onClick={(e) => handleDeleteClick(e, project.name)}
className="p-2 mr-2 text-[var(--color-neo-text-secondary)] hover:text-[var(--color-neo-danger)] hover:bg-[var(--color-neo-danger)]/10 transition-colors rounded"
title={`Delete ${project.name}`}
>
<Trash2 size={16} />
</button>
</div>
))}
</div>
) : (
@@ -124,6 +167,19 @@ export function ProjectSelector({
onClose={() => setShowNewProjectModal(false)}
onProjectCreated={handleProjectCreated}
/>
{/* Delete Confirmation Dialog */}
<ConfirmDialog
isOpen={projectToDelete !== null}
title="Delete Project"
message={`Are you sure you want to remove "${projectToDelete}" from the registry? This will unregister the project but preserve its files on disk.`}
confirmLabel="Delete"
cancelLabel="Cancel"
variant="danger"
isLoading={deleteProject.isPending}
onConfirm={handleConfirmDelete}
onCancel={handleCancelDelete}
/>
</div>
)
}

View File

@@ -43,7 +43,7 @@ export function SpecCreationChat({
const [yoloEnabled, setYoloEnabled] = useState(false)
const [pendingAttachments, setPendingAttachments] = useState<ImageAttachment[]>([])
const messagesEndRef = useRef<HTMLDivElement>(null)
const inputRef = useRef<HTMLInputElement>(null)
const inputRef = useRef<HTMLTextAreaElement>(null)
const fileInputRef = useRef<HTMLInputElement>(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({
<Paperclip size={18} />
</button>
<input
<textarea
ref={inputRef}
type="text"
value={input}
onChange={(e) => 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}
/>
<button
onClick={handleSendMessage}
@@ -386,7 +395,7 @@ export function SpecCreationChat({
{/* Help text */}
<p className="text-xs text-[var(--color-neo-text-secondary)] mt-2">
Press Enter to send. Drag & drop or click <Paperclip size={12} className="inline" /> to attach images (JPEG/PNG, max 5MB).
Press Enter to send, Shift+Enter for new line. Drag & drop or click <Paperclip size={12} className="inline" /> to attach images (JPEG/PNG, max 5MB).
</p>
</div>
)}

View File

@@ -1 +1 @@
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/addfeatureform.tsx","./src/components/agentcontrol.tsx","./src/components/agentthought.tsx","./src/components/assistantchat.tsx","./src/components/assistantfab.tsx","./src/components/assistantpanel.tsx","./src/components/chatmessage.tsx","./src/components/debuglogviewer.tsx","./src/components/expandprojectchat.tsx","./src/components/expandprojectmodal.tsx","./src/components/featurecard.tsx","./src/components/featuremodal.tsx","./src/components/folderbrowser.tsx","./src/components/kanbanboard.tsx","./src/components/kanbancolumn.tsx","./src/components/newprojectmodal.tsx","./src/components/progressdashboard.tsx","./src/components/projectselector.tsx","./src/components/questionoptions.tsx","./src/components/settingsmodal.tsx","./src/components/setupwizard.tsx","./src/components/speccreationchat.tsx","./src/components/typingindicator.tsx","./src/hooks/useassistantchat.ts","./src/hooks/usecelebration.ts","./src/hooks/useexpandchat.ts","./src/hooks/usefeaturesound.ts","./src/hooks/useprojects.ts","./src/hooks/usespecchat.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/types.ts"],"version":"5.6.3"}
{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/addfeatureform.tsx","./src/components/agentcontrol.tsx","./src/components/agentthought.tsx","./src/components/assistantchat.tsx","./src/components/assistantfab.tsx","./src/components/assistantpanel.tsx","./src/components/chatmessage.tsx","./src/components/confirmdialog.tsx","./src/components/debuglogviewer.tsx","./src/components/expandprojectchat.tsx","./src/components/expandprojectmodal.tsx","./src/components/featurecard.tsx","./src/components/featuremodal.tsx","./src/components/folderbrowser.tsx","./src/components/kanbanboard.tsx","./src/components/kanbancolumn.tsx","./src/components/newprojectmodal.tsx","./src/components/progressdashboard.tsx","./src/components/projectselector.tsx","./src/components/questionoptions.tsx","./src/components/settingsmodal.tsx","./src/components/setupwizard.tsx","./src/components/speccreationchat.tsx","./src/components/typingindicator.tsx","./src/hooks/useassistantchat.ts","./src/hooks/usecelebration.ts","./src/hooks/useexpandchat.ts","./src/hooks/usefeaturesound.ts","./src/hooks/useprojects.ts","./src/hooks/usespecchat.ts","./src/hooks/usewebsocket.ts","./src/lib/api.ts","./src/lib/types.ts"],"version":"5.6.3"}