From d48fb0a6fce2a8a3a228e706e7c9e80817f80cb3 Mon Sep 17 00:00:00 2001 From: mmereu Date: Sat, 24 Jan 2026 10:40:47 +0100 Subject: [PATCH 01/63] fix: prevent agent subprocess blocking on Windows - Add stdin=subprocess.DEVNULL to prevent blocking on stdin reads - Add CREATE_NO_WINDOW flag on Windows to prevent console pop-ups - Remove trailing pause from start_ui.bat Co-Authored-By: Claude Opus 4.5 --- parallel_orchestrator.py | 21 +++++++++++++-------- server/services/process_manager.py | 18 ++++++++++++------ start_ui.bat | 2 -- 3 files changed, 25 insertions(+), 16 deletions(-) diff --git a/parallel_orchestrator.py b/parallel_orchestrator.py index da348c8..91e7be0 100644 --- a/parallel_orchestrator.py +++ b/parallel_orchestrator.py @@ -299,14 +299,19 @@ class ParallelOrchestrator: cmd.append("--yolo") try: - proc = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - text=True, - cwd=str(AUTOCODER_ROOT), # Run from autocoder root for proper imports - env={**os.environ, "PYTHONUNBUFFERED": "1"}, - ) + # CREATE_NO_WINDOW on Windows prevents console window pop-ups + popen_kwargs = { + "stdin": subprocess.DEVNULL, + "stdout": subprocess.PIPE, + "stderr": subprocess.STDOUT, + "text": True, + "cwd": str(AUTOCODER_ROOT), # Run from autocoder root for proper imports + "env": {**os.environ, "PYTHONUNBUFFERED": "1"}, + } + if sys.platform == "win32": + popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW + + proc = subprocess.Popen(cmd, **popen_kwargs) except Exception as e: # Reset in_progress on failure session = self.get_session() diff --git a/server/services/process_manager.py b/server/services/process_manager.py index 2dc1137..01b9b47 100644 --- a/server/services/process_manager.py +++ b/server/services/process_manager.py @@ -341,12 +341,18 @@ class AgentProcessManager: try: # Start subprocess with piped stdout/stderr # Use project_dir as cwd so Claude SDK sandbox allows access to project files - self.process = subprocess.Popen( - cmd, - stdout=subprocess.PIPE, - stderr=subprocess.STDOUT, - cwd=str(self.project_dir), - ) + # stdin=DEVNULL prevents blocking if Claude CLI or child process tries to read stdin + # CREATE_NO_WINDOW on Windows prevents console window pop-ups + popen_kwargs = { + "stdin": subprocess.DEVNULL, + "stdout": subprocess.PIPE, + "stderr": subprocess.STDOUT, + "cwd": str(self.project_dir), + } + if sys.platform == "win32": + popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW + + self.process = subprocess.Popen(cmd, **popen_kwargs) # Atomic lock creation - if it fails, another process beat us if not self._create_lock(): diff --git a/start_ui.bat b/start_ui.bat index 2c59753..c8ad646 100644 --- a/start_ui.bat +++ b/start_ui.bat @@ -39,5 +39,3 @@ pip install -r requirements.txt --quiet REM Run the Python launcher python "%~dp0start_ui.py" %* - -pause From 795bd5f92a8980244f6c093c38e4273b00eba678 Mon Sep 17 00:00:00 2001 From: mmereu Date: Sat, 24 Jan 2026 11:18:28 +0100 Subject: [PATCH 02/63] fix: kill process tree on agent completion to prevent zombies Added _kill_process_tree call in _read_output finally block to ensure child processes (Claude CLI) are cleaned up when agents complete or fail. This prevents accumulation of zombie processes that was causing 78+ Python processes when max concurrency was set to 5. Co-Authored-By: Claude Opus 4.5 --- parallel_orchestrator.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/parallel_orchestrator.py b/parallel_orchestrator.py index 74ce84f..98778c1 100644 --- a/parallel_orchestrator.py +++ b/parallel_orchestrator.py @@ -632,6 +632,12 @@ class ParallelOrchestrator: print(f"[Feature #{feature_id}] {line}", flush=True) proc.wait() finally: + # CRITICAL: Kill the process tree to clean up any child processes (e.g., Claude CLI) + # This prevents zombie processes from accumulating + try: + _kill_process_tree(proc, timeout=2.0) + except Exception as e: + debug_log.log("CLEANUP", f"Error killing process tree for {agent_type} agent", error=str(e)) self._on_agent_complete(feature_id, proc.returncode, agent_type, proc) def _on_agent_complete( From 8bc4b25511c2a13282ffa3ec3da2a0489c5dbb53 Mon Sep 17 00:00:00 2001 From: nioasoft Date: Sat, 24 Jan 2026 09:53:25 +0200 Subject: [PATCH 03/63] feat(ui): add custom theme override system Create custom-theme.css for theme overrides that won't conflict with upstream updates. The file loads after globals.css, so its CSS variables take precedence. This approach ensures: - Zero merge conflicts on git pull (new file, not in upstream) - Theme persists across upstream updates - Easy to modify without touching upstream code Co-Authored-By: Claude Opus 4.5 --- ui/src/main.tsx | 1 + ui/src/styles/custom-theme.css | 170 +++++++++++++++++++++++++++++++++ 2 files changed, 171 insertions(+) create mode 100644 ui/src/styles/custom-theme.css diff --git a/ui/src/main.tsx b/ui/src/main.tsx index e8d9888..0420f66 100644 --- a/ui/src/main.tsx +++ b/ui/src/main.tsx @@ -3,6 +3,7 @@ import { createRoot } from 'react-dom/client' import { QueryClient, QueryClientProvider } from '@tanstack/react-query' import App from './App' import './styles/globals.css' +import './styles/custom-theme.css' // Custom theme overrides (safe from upstream conflicts) const queryClient = new QueryClient({ defaultOptions: { diff --git a/ui/src/styles/custom-theme.css b/ui/src/styles/custom-theme.css new file mode 100644 index 0000000..218dc03 --- /dev/null +++ b/ui/src/styles/custom-theme.css @@ -0,0 +1,170 @@ +/* + * Custom Theme Overrides + * ====================== + * This file overrides the default neobrutalism theme. + * It loads AFTER globals.css, so these values take precedence. + * + * This file is safe from upstream merge conflicts since it doesn't + * exist in the upstream repository. + */ + +:root { + --background: oklch(1.0000 0 0); + --foreground: oklch(0.1884 0.0128 248.5103); + --card: oklch(0.9784 0.0011 197.1387); + --card-foreground: oklch(0.1884 0.0128 248.5103); + --popover: oklch(1.0000 0 0); + --popover-foreground: oklch(0.1884 0.0128 248.5103); + --primary: oklch(0.6723 0.1606 244.9955); + --primary-foreground: oklch(1.0000 0 0); + --secondary: oklch(0.1884 0.0128 248.5103); + --secondary-foreground: oklch(1.0000 0 0); + --muted: oklch(0.9222 0.0013 286.3737); + --muted-foreground: oklch(0.1884 0.0128 248.5103); + --accent: oklch(0.9392 0.0166 250.8453); + --accent-foreground: oklch(0.6723 0.1606 244.9955); + --destructive: oklch(0.6188 0.2376 25.7658); + --destructive-foreground: oklch(1.0000 0 0); + --border: oklch(0.9317 0.0118 231.6594); + --input: oklch(0.9809 0.0025 228.7836); + --ring: oklch(0.6818 0.1584 243.3540); + --chart-1: oklch(0.6723 0.1606 244.9955); + --chart-2: oklch(0.6907 0.1554 160.3454); + --chart-3: oklch(0.8214 0.1600 82.5337); + --chart-4: oklch(0.7064 0.1822 151.7125); + --chart-5: oklch(0.5919 0.2186 10.5826); + --sidebar: oklch(0.9784 0.0011 197.1387); + --sidebar-foreground: oklch(0.1884 0.0128 248.5103); + --sidebar-primary: oklch(0.6723 0.1606 244.9955); + --sidebar-primary-foreground: oklch(1.0000 0 0); + --sidebar-accent: oklch(0.9392 0.0166 250.8453); + --sidebar-accent-foreground: oklch(0.6723 0.1606 244.9955); + --sidebar-border: oklch(0.9271 0.0101 238.5177); + --sidebar-ring: oklch(0.6818 0.1584 243.3540); + --font-sans: Open Sans, sans-serif; + --font-serif: Georgia, serif; + --font-mono: Menlo, monospace; + --radius: 1.3rem; + --shadow-x: 0px; + --shadow-y: 2px; + --shadow-blur: 0px; + --shadow-spread: 0px; + --shadow-opacity: 0; + --shadow-color: rgba(29,161,242,0.15); + --shadow-2xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-sm: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-md: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 2px 4px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-lg: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 4px 6px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 8px 10px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-2xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); + --tracking-normal: 0em; + --spacing: 0.25rem; +} + +.dark { + --background: oklch(0 0 0); + --foreground: oklch(0.9328 0.0025 228.7857); + --card: oklch(0.2097 0.0080 274.5332); + --card-foreground: oklch(0.8853 0 0); + --popover: oklch(0 0 0); + --popover-foreground: oklch(0.9328 0.0025 228.7857); + --primary: oklch(0.6692 0.1607 245.0110); + --primary-foreground: oklch(1.0000 0 0); + --secondary: oklch(0.9622 0.0035 219.5331); + --secondary-foreground: oklch(0.1884 0.0128 248.5103); + --muted: oklch(0.2090 0 0); + --muted-foreground: oklch(0.5637 0.0078 247.9662); + --accent: oklch(0.1928 0.0331 242.5459); + --accent-foreground: oklch(0.6692 0.1607 245.0110); + --destructive: oklch(0.6188 0.2376 25.7658); + --destructive-foreground: oklch(1.0000 0 0); + --border: oklch(0.2674 0.0047 248.0045); + --input: oklch(0.3020 0.0288 244.8244); + --ring: oklch(0.6818 0.1584 243.3540); + --chart-1: oklch(0.6723 0.1606 244.9955); + --chart-2: oklch(0.6907 0.1554 160.3454); + --chart-3: oklch(0.8214 0.1600 82.5337); + --chart-4: oklch(0.7064 0.1822 151.7125); + --chart-5: oklch(0.5919 0.2186 10.5826); + --sidebar: oklch(0.2097 0.0080 274.5332); + --sidebar-foreground: oklch(0.8853 0 0); + --sidebar-primary: oklch(0.6818 0.1584 243.3540); + --sidebar-primary-foreground: oklch(1.0000 0 0); + --sidebar-accent: oklch(0.1928 0.0331 242.5459); + --sidebar-accent-foreground: oklch(0.6692 0.1607 245.0110); + --sidebar-border: oklch(0.3795 0.0220 240.5943); + --sidebar-ring: oklch(0.6818 0.1584 243.3540); + --font-sans: Open Sans, sans-serif; + --font-serif: Georgia, serif; + --font-mono: Menlo, monospace; + --radius: 1.3rem; + --shadow-x: 0px; + --shadow-y: 2px; + --shadow-blur: 0px; + --shadow-spread: 0px; + --shadow-opacity: 0; + --shadow-color: rgba(29,161,242,0.25); + --shadow-2xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-sm: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-md: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 2px 4px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-lg: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 4px 6px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 8px 10px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); + --shadow-2xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-destructive-foreground: var(--destructive-foreground); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); + + --font-sans: var(--font-sans); + --font-mono: var(--font-mono); + --font-serif: var(--font-serif); + + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + + --shadow-2xs: var(--shadow-2xs); + --shadow-xs: var(--shadow-xs); + --shadow-sm: var(--shadow-sm); + --shadow: var(--shadow); + --shadow-md: var(--shadow-md); + --shadow-lg: var(--shadow-lg); + --shadow-xl: var(--shadow-xl); + --shadow-2xl: var(--shadow-2xl); +} From 813bb900fd6f214f8e14ff8945f53695c90d6f18 Mon Sep 17 00:00:00 2001 From: nioasoft Date: Sat, 24 Jan 2026 10:39:34 +0200 Subject: [PATCH 04/63] feat: Twitter-style UI theme + Playwright optimization + documentation UI Changes: - Replace neobrutalism with clean Twitter/Supabase-style design - Remove all shadows, use thin borders (1px) - Single accent color (Twitter blue) for all status indicators - Rounded corners (1.3rem base) - Fix dark mode contrast and visibility - Make KanbanColumn themeable via CSS classes Backend Changes: - Default Playwright browser changed to Firefox (lower CPU) - Default Playwright mode changed to headless (saves resources) - Add PLAYWRIGHT_BROWSER env var support Documentation: - Add CUSTOM_UPDATES.md with all customizations documented - Update .env.example with new Playwright options Co-Authored-By: Claude Opus 4.5 --- .env.example | 17 +- CUSTOM_UPDATES.md | 328 +++++++++++++++++ client.py | 36 +- ui/src/components/KanbanColumn.tsx | 16 +- ui/src/styles/custom-theme.css | 559 +++++++++++++++++++++-------- 5 files changed, 776 insertions(+), 180 deletions(-) create mode 100644 CUSTOM_UPDATES.md diff --git a/.env.example b/.env.example index e29bec3..6457cbf 100644 --- a/.env.example +++ b/.env.example @@ -1,12 +1,19 @@ # Optional: N8N webhook for progress notifications # PROGRESS_N8N_WEBHOOK_URL=https://your-n8n-instance.com/webhook/... -# 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) +# Playwright Browser Configuration +# +# PLAYWRIGHT_BROWSER: Which browser to use for testing +# - firefox: Lower CPU usage, recommended (default) +# - chrome: Google Chrome +# - webkit: Safari engine +# - msedge: Microsoft Edge +# PLAYWRIGHT_BROWSER=firefox +# +# PLAYWRIGHT_HEADLESS: Run browser without visible window +# - true: Browser runs in background, saves CPU (default) # - false: Browser opens a visible window (useful for debugging) -# Defaults to 'false' if not specified -# PLAYWRIGHT_HEADLESS=false +# PLAYWRIGHT_HEADLESS=true # GLM/Alternative API Configuration (Optional) # To use Zhipu AI's GLM models instead of Claude, uncomment and set these variables. diff --git a/CUSTOM_UPDATES.md b/CUSTOM_UPDATES.md new file mode 100644 index 0000000..9a3bd4e --- /dev/null +++ b/CUSTOM_UPDATES.md @@ -0,0 +1,328 @@ +# Custom Updates - AutoCoder + +This document tracks all customizations made to AutoCoder that deviate from the upstream repository. Reference this file before any updates to preserve these changes. + +--- + +## Table of Contents + +1. [UI Theme Customization](#1-ui-theme-customization) +2. [Playwright Browser Configuration](#2-playwright-browser-configuration) +3. [SQLite Robust Connection Handling](#3-sqlite-robust-connection-handling) +4. [Update Checklist](#update-checklist) + +--- + +## 1. UI Theme Customization + +### Overview + +The UI has been customized from the default **neobrutalism** style to a clean **Twitter/Supabase-style** design. + +**Design Changes:** +- No shadows +- Thin borders (1px) +- Rounded corners (1.3rem base) +- Blue accent color (Twitter blue) +- Clean typography (Open Sans) + +### Modified Files + +#### `ui/src/styles/custom-theme.css` + +**Purpose:** Main theme override file that replaces neo design with clean Twitter style. + +**Key Changes:** +- All `--shadow-neo-*` variables set to `none` +- All status colors (`pending`, `progress`, `done`) use Twitter blue +- Rounded corners: `--radius-neo-lg: 1.3rem` +- Font: Open Sans +- Removed all transform effects on hover +- Dark mode with proper contrast + +**CSS Variables (Light Mode):** +```css +--color-neo-accent: oklch(0.6723 0.1606 244.9955); /* Twitter blue */ +--color-neo-pending: oklch(0.6723 0.1606 244.9955); +--color-neo-progress: oklch(0.6723 0.1606 244.9955); +--color-neo-done: oklch(0.6723 0.1606 244.9955); +``` + +**CSS Variables (Dark Mode):** +```css +--color-neo-bg: oklch(0.08 0 0); +--color-neo-card: oklch(0.16 0.005 250); +--color-neo-border: oklch(0.30 0 0); +``` + +**How to preserve:** This file should NOT be overwritten. It loads after `globals.css` and overrides it. + +--- + +#### `ui/src/components/KanbanColumn.tsx` + +**Purpose:** Modified to support themeable kanban columns without inline styles. + +**Changes:** + +1. **colorMap changed from inline colors to CSS classes:** +```tsx +// BEFORE (original): +const colorMap = { + pending: 'var(--color-neo-pending)', + progress: 'var(--color-neo-progress)', + done: 'var(--color-neo-done)', +} + +// AFTER (customized): +const colorMap = { + pending: 'kanban-header-pending', + progress: 'kanban-header-progress', + done: 'kanban-header-done', +} +``` + +2. **Column div uses CSS class instead of inline style:** +```tsx +// BEFORE: +
+ +// AFTER: +
+``` + +3. **Header div simplified (removed duplicate color class):** +```tsx +// BEFORE: +
+ +// AFTER: +
+``` + +4. **Title text color:** +```tsx +// BEFORE: +text-[var(--color-neo-text-on-bright)] + +// AFTER: +text-[var(--color-neo-text)] +``` + +--- + +## 2. Playwright Browser Configuration + +### Overview + +Changed default Playwright settings for better performance: +- **Default browser:** Firefox (lower CPU usage) +- **Default mode:** Headless (saves resources) + +### Modified Files + +#### `client.py` + +**Changes:** + +```python +# BEFORE: +DEFAULT_PLAYWRIGHT_HEADLESS = False + +# AFTER: +DEFAULT_PLAYWRIGHT_HEADLESS = True +DEFAULT_PLAYWRIGHT_BROWSER = "firefox" +``` + +**New function added:** +```python +def get_playwright_browser() -> str: + """ + Get the browser to use for Playwright. + Options: chrome, firefox, webkit, msedge + Firefox is recommended for lower CPU usage. + """ + return os.getenv("PLAYWRIGHT_BROWSER", DEFAULT_PLAYWRIGHT_BROWSER).lower() +``` + +**Playwright args updated:** +```python +playwright_args = [ + "@playwright/mcp@latest", + "--viewport-size", "1280x720", + "--browser", browser, # NEW: configurable browser +] +``` + +--- + +#### `.env.example` + +**Updated documentation:** +```bash +# PLAYWRIGHT_BROWSER: Which browser to use for testing +# - firefox: Lower CPU usage, recommended (default) +# - chrome: Google Chrome +# - webkit: Safari engine +# - msedge: Microsoft Edge +# PLAYWRIGHT_BROWSER=firefox + +# PLAYWRIGHT_HEADLESS: Run browser without visible window +# - true: Browser runs in background, saves CPU (default) +# - false: Browser opens a visible window (useful for debugging) +# PLAYWRIGHT_HEADLESS=true +``` + +--- + +## 3. SQLite Robust Connection Handling + +### Overview + +Added robust SQLite connection handling to prevent database corruption from concurrent access (MCP server, FastAPI server, progress tracking). + +**Features Added:** +- WAL mode for better concurrency +- Busy timeout (30 seconds) +- Retry logic with exponential backoff +- Database health check endpoint + +### Modified Files + +#### `api/database.py` + +**New functions added:** + +```python +def get_robust_connection(db_path: str) -> sqlite3.Connection: + """ + Create a SQLite connection with robust settings: + - WAL mode for concurrent access + - 30 second busy timeout + - Foreign keys enabled + """ + +@contextmanager +def robust_db_connection(db_path: str): + """Context manager for robust database connections.""" + +def execute_with_retry(conn, sql, params=None, max_retries=3): + """Execute SQL with exponential backoff retry for transient errors.""" + +def check_database_health(db_path: str) -> dict: + """ + Check database integrity and return health status. + Returns: {healthy: bool, message: str, details: dict} + """ +``` + +--- + +#### `progress.py` + +**Changed from raw sqlite3 to robust connections:** + +```python +# BEFORE: +conn = sqlite3.connect(db_path) + +# AFTER: +from api.database import robust_db_connection, execute_with_retry + +with robust_db_connection(db_path) as conn: + execute_with_retry(conn, sql, params) +``` + +--- + +#### `server/routers/projects.py` + +**New endpoint added:** + +```python +@router.get("/{project_name}/db-health") +async def get_database_health(project_name: str) -> DatabaseHealth: + """ + Check the health of the project's features database. + Useful for diagnosing corruption issues. + """ +``` + +--- + +#### `server/schemas.py` + +**New schema added:** + +```python +class DatabaseHealth(BaseModel): + healthy: bool + message: str + details: dict = {} +``` + +--- + +## Update Checklist + +When updating AutoCoder from upstream, verify these items: + +### UI Changes +- [ ] `ui/src/styles/custom-theme.css` is preserved +- [ ] `ui/src/components/KanbanColumn.tsx` changes are preserved +- [ ] Run `npm run build` in `ui/` directory +- [ ] Test both light and dark modes + +### Backend Changes +- [ ] `client.py` - Playwright browser/headless defaults preserved +- [ ] `.env.example` - Documentation updates preserved +- [ ] `api/database.py` - Robust connection functions preserved +- [ ] `progress.py` - Uses robust_db_connection +- [ ] `server/routers/projects.py` - db-health endpoint preserved +- [ ] `server/schemas.py` - DatabaseHealth schema preserved + +### General +- [ ] Test database operations under concurrent load +- [ ] Verify Playwright uses Firefox by default +- [ ] Check that browser runs headless by default + +--- + +## Reverting to Defaults + +### UI Only +```bash +rm ui/src/styles/custom-theme.css +git checkout ui/src/components/KanbanColumn.tsx +cd ui && npm run build +``` + +### Backend Only +```bash +git checkout client.py .env.example api/database.py progress.py +git checkout server/routers/projects.py server/schemas.py +``` + +--- + +## Files Summary + +| File | Type | Change Description | +|------|------|-------------------| +| `ui/src/styles/custom-theme.css` | UI | Twitter-style theme | +| `ui/src/components/KanbanColumn.tsx` | UI | Themeable kanban columns | +| `client.py` | Backend | Firefox + headless defaults | +| `.env.example` | Config | Updated documentation | +| `api/database.py` | Backend | Robust SQLite connections | +| `progress.py` | Backend | Uses robust connections | +| `server/routers/projects.py` | Backend | db-health endpoint | +| `server/schemas.py` | Backend | DatabaseHealth schema | + +--- + +## Last Updated + +**Date:** January 2026 +**Commits:** +- `1910b96` - SQLite robust connection handling +- `e014b04` - Custom theme override system diff --git a/client.py b/client.py index e844aa4..4bf3669 100644 --- a/client.py +++ b/client.py @@ -21,9 +21,14 @@ from security import bash_security_hook load_dotenv() # 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 +# When True, browser runs invisibly in background (default - saves CPU) +# When False, browser window is visible (useful for monitoring agent progress) +DEFAULT_PLAYWRIGHT_HEADLESS = True + +# Default browser for Playwright - can be overridden via PLAYWRIGHT_BROWSER env var +# Options: chrome, firefox, webkit, msedge +# Firefox is recommended for lower CPU usage +DEFAULT_PLAYWRIGHT_BROWSER = "firefox" # Environment variables to pass through to Claude CLI for API configuration # These allow using alternative API endpoints (e.g., GLM via z.ai) without @@ -42,14 +47,25 @@ def get_playwright_headless() -> bool: """ Get the Playwright headless mode setting. - Reads from PLAYWRIGHT_HEADLESS environment variable, defaults to False. + Reads from PLAYWRIGHT_HEADLESS environment variable, defaults to True. Returns True for headless mode (invisible browser), False for visible browser. """ - value = os.getenv("PLAYWRIGHT_HEADLESS", "false").lower() + value = os.getenv("PLAYWRIGHT_HEADLESS", str(DEFAULT_PLAYWRIGHT_HEADLESS).lower()).lower() # Accept various truthy/falsy values return value in ("true", "1", "yes", "on") +def get_playwright_browser() -> str: + """ + Get the browser to use for Playwright. + + Reads from PLAYWRIGHT_BROWSER environment variable, defaults to firefox. + Options: chrome, firefox, webkit, msedge + Firefox is recommended for lower CPU usage. + """ + return os.getenv("PLAYWRIGHT_BROWSER", DEFAULT_PLAYWRIGHT_BROWSER).lower() + + # Feature MCP tools for feature/test management FEATURE_MCP_TOOLS = [ # Core feature operations @@ -228,10 +244,16 @@ def create_client( } 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"] + # Browser and headless mode configurable via environment variables + browser = get_playwright_browser() + playwright_args = [ + "@playwright/mcp@latest", + "--viewport-size", "1280x720", + "--browser", browser, + ] if get_playwright_headless(): playwright_args.append("--headless") + print(f" - Browser: {browser} (headless={get_playwright_headless()})") # Browser isolation for parallel execution # Each agent gets its own isolated browser context to prevent tab conflicts diff --git a/ui/src/components/KanbanColumn.tsx b/ui/src/components/KanbanColumn.tsx index 340f64f..191ac5a 100644 --- a/ui/src/components/KanbanColumn.tsx +++ b/ui/src/components/KanbanColumn.tsx @@ -18,9 +18,9 @@ interface KanbanColumnProps { } const colorMap = { - pending: 'var(--color-neo-pending)', - progress: 'var(--color-neo-progress)', - done: 'var(--color-neo-done)', + pending: 'kanban-header-pending', + progress: 'kanban-header-progress', + done: 'kanban-header-done', } export function KanbanColumn({ @@ -43,18 +43,16 @@ export function KanbanColumn({ ) return (
{/* Header */}
-

+

{title} - {count} + {count}

{(onAddFeature || onExpandProject) && (
diff --git a/ui/src/styles/custom-theme.css b/ui/src/styles/custom-theme.css index 218dc03..69748ba 100644 --- a/ui/src/styles/custom-theme.css +++ b/ui/src/styles/custom-theme.css @@ -1,170 +1,411 @@ /* - * Custom Theme Overrides - * ====================== - * This file overrides the default neobrutalism theme. - * It loads AFTER globals.css, so these values take precedence. - * - * This file is safe from upstream merge conflicts since it doesn't - * exist in the upstream repository. + * Clean Twitter-Style Theme + * ========================= + * Based on user's exact design system values */ :root { - --background: oklch(1.0000 0 0); - --foreground: oklch(0.1884 0.0128 248.5103); - --card: oklch(0.9784 0.0011 197.1387); - --card-foreground: oklch(0.1884 0.0128 248.5103); - --popover: oklch(1.0000 0 0); - --popover-foreground: oklch(0.1884 0.0128 248.5103); - --primary: oklch(0.6723 0.1606 244.9955); - --primary-foreground: oklch(1.0000 0 0); - --secondary: oklch(0.1884 0.0128 248.5103); - --secondary-foreground: oklch(1.0000 0 0); - --muted: oklch(0.9222 0.0013 286.3737); - --muted-foreground: oklch(0.1884 0.0128 248.5103); - --accent: oklch(0.9392 0.0166 250.8453); - --accent-foreground: oklch(0.6723 0.1606 244.9955); - --destructive: oklch(0.6188 0.2376 25.7658); - --destructive-foreground: oklch(1.0000 0 0); - --border: oklch(0.9317 0.0118 231.6594); - --input: oklch(0.9809 0.0025 228.7836); - --ring: oklch(0.6818 0.1584 243.3540); - --chart-1: oklch(0.6723 0.1606 244.9955); - --chart-2: oklch(0.6907 0.1554 160.3454); - --chart-3: oklch(0.8214 0.1600 82.5337); - --chart-4: oklch(0.7064 0.1822 151.7125); - --chart-5: oklch(0.5919 0.2186 10.5826); - --sidebar: oklch(0.9784 0.0011 197.1387); - --sidebar-foreground: oklch(0.1884 0.0128 248.5103); - --sidebar-primary: oklch(0.6723 0.1606 244.9955); - --sidebar-primary-foreground: oklch(1.0000 0 0); - --sidebar-accent: oklch(0.9392 0.0166 250.8453); - --sidebar-accent-foreground: oklch(0.6723 0.1606 244.9955); - --sidebar-border: oklch(0.9271 0.0101 238.5177); - --sidebar-ring: oklch(0.6818 0.1584 243.3540); - --font-sans: Open Sans, sans-serif; - --font-serif: Georgia, serif; - --font-mono: Menlo, monospace; - --radius: 1.3rem; - --shadow-x: 0px; - --shadow-y: 2px; - --shadow-blur: 0px; - --shadow-spread: 0px; - --shadow-opacity: 0; - --shadow-color: rgba(29,161,242,0.15); - --shadow-2xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-sm: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-md: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 2px 4px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-lg: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 4px 6px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 8px 10px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-2xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); - --tracking-normal: 0em; - --spacing: 0.25rem; + /* Core colors */ + --color-neo-bg: oklch(1.0000 0 0); + --color-neo-card: oklch(0.9784 0.0011 197.1387); + --color-neo-text: oklch(0.1884 0.0128 248.5103); + --color-neo-text-secondary: oklch(0.1884 0.0128 248.5103); + --color-neo-text-muted: oklch(0.5637 0.0078 247.9662); + --color-neo-text-on-bright: oklch(1.0000 0 0); + + /* Primary accent - Twitter blue */ + --color-neo-accent: oklch(0.6723 0.1606 244.9955); + + /* Status colors - all use accent blue except danger */ + --color-neo-pending: oklch(0.6723 0.1606 244.9955); + --color-neo-progress: oklch(0.6723 0.1606 244.9955); + --color-neo-done: oklch(0.6723 0.1606 244.9955); + --color-neo-danger: oklch(0.6188 0.2376 25.7658); + + /* Borders and neutrals */ + --color-neo-border: oklch(0.9317 0.0118 231.6594); + --color-neo-neutral-50: oklch(0.9809 0.0025 228.7836); + --color-neo-neutral-100: oklch(0.9392 0.0166 250.8453); + --color-neo-neutral-200: oklch(0.9222 0.0013 286.3737); + --color-neo-neutral-300: oklch(0.9317 0.0118 231.6594); + + /* No shadows */ + --shadow-neo-sm: none; + --shadow-neo-md: none; + --shadow-neo-lg: none; + --shadow-neo-xl: none; + --shadow-neo-left: none; + --shadow-neo-inset: none; + + /* Typography */ + --font-neo-sans: Open Sans, sans-serif; + --font-neo-mono: Menlo, monospace; + + /* Radius - 1.3rem base */ + --radius-neo-sm: calc(1.3rem - 4px); + --radius-neo-md: calc(1.3rem - 2px); + --radius-neo-lg: 1.3rem; + --radius-neo-xl: calc(1.3rem + 4px); } .dark { - --background: oklch(0 0 0); - --foreground: oklch(0.9328 0.0025 228.7857); - --card: oklch(0.2097 0.0080 274.5332); - --card-foreground: oklch(0.8853 0 0); - --popover: oklch(0 0 0); - --popover-foreground: oklch(0.9328 0.0025 228.7857); - --primary: oklch(0.6692 0.1607 245.0110); - --primary-foreground: oklch(1.0000 0 0); - --secondary: oklch(0.9622 0.0035 219.5331); - --secondary-foreground: oklch(0.1884 0.0128 248.5103); - --muted: oklch(0.2090 0 0); - --muted-foreground: oklch(0.5637 0.0078 247.9662); - --accent: oklch(0.1928 0.0331 242.5459); - --accent-foreground: oklch(0.6692 0.1607 245.0110); - --destructive: oklch(0.6188 0.2376 25.7658); - --destructive-foreground: oklch(1.0000 0 0); - --border: oklch(0.2674 0.0047 248.0045); - --input: oklch(0.3020 0.0288 244.8244); - --ring: oklch(0.6818 0.1584 243.3540); - --chart-1: oklch(0.6723 0.1606 244.9955); - --chart-2: oklch(0.6907 0.1554 160.3454); - --chart-3: oklch(0.8214 0.1600 82.5337); - --chart-4: oklch(0.7064 0.1822 151.7125); - --chart-5: oklch(0.5919 0.2186 10.5826); - --sidebar: oklch(0.2097 0.0080 274.5332); - --sidebar-foreground: oklch(0.8853 0 0); - --sidebar-primary: oklch(0.6818 0.1584 243.3540); - --sidebar-primary-foreground: oklch(1.0000 0 0); - --sidebar-accent: oklch(0.1928 0.0331 242.5459); - --sidebar-accent-foreground: oklch(0.6692 0.1607 245.0110); - --sidebar-border: oklch(0.3795 0.0220 240.5943); - --sidebar-ring: oklch(0.6818 0.1584 243.3540); - --font-sans: Open Sans, sans-serif; - --font-serif: Georgia, serif; - --font-mono: Menlo, monospace; - --radius: 1.3rem; - --shadow-x: 0px; - --shadow-y: 2px; - --shadow-blur: 0px; - --shadow-spread: 0px; - --shadow-opacity: 0; - --shadow-color: rgba(29,161,242,0.25); - --shadow-2xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-xs: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-sm: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 1px 2px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-md: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 2px 4px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-lg: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 4px 6px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00), 0px 8px 10px -1px hsl(202.8169 89.1213% 53.1373% / 0.00); - --shadow-2xl: 0px 2px 0px 0px hsl(202.8169 89.1213% 53.1373% / 0.00); + /* Core colors - dark mode (Twitter dark style) */ + --color-neo-bg: oklch(0.08 0 0); + --color-neo-card: oklch(0.16 0.005 250); + --color-neo-text: oklch(0.95 0 0); + --color-neo-text-secondary: oklch(0.75 0 0); + --color-neo-text-muted: oklch(0.55 0 0); + --color-neo-text-on-bright: oklch(1.0 0 0); + + /* Primary accent */ + --color-neo-accent: oklch(0.6692 0.1607 245.0110); + + /* Status colors - all use accent blue except danger */ + --color-neo-pending: oklch(0.6692 0.1607 245.0110); + --color-neo-progress: oklch(0.6692 0.1607 245.0110); + --color-neo-done: oklch(0.6692 0.1607 245.0110); + --color-neo-danger: oklch(0.6188 0.2376 25.7658); + + /* Borders and neutrals - better contrast */ + --color-neo-border: oklch(0.30 0 0); + --color-neo-neutral-50: oklch(0.20 0 0); + --color-neo-neutral-100: oklch(0.25 0.01 250); + --color-neo-neutral-200: oklch(0.22 0 0); + --color-neo-neutral-300: oklch(0.30 0 0); + + /* No shadows */ + --shadow-neo-sm: none; + --shadow-neo-md: none; + --shadow-neo-lg: none; + --shadow-neo-xl: none; + --shadow-neo-left: none; + --shadow-neo-inset: none; } -@theme inline { - --color-background: var(--background); - --color-foreground: var(--foreground); - --color-card: var(--card); - --color-card-foreground: var(--card-foreground); - --color-popover: var(--popover); - --color-popover-foreground: var(--popover-foreground); - --color-primary: var(--primary); - --color-primary-foreground: var(--primary-foreground); - --color-secondary: var(--secondary); - --color-secondary-foreground: var(--secondary-foreground); - --color-muted: var(--muted); - --color-muted-foreground: var(--muted-foreground); - --color-accent: var(--accent); - --color-accent-foreground: var(--accent-foreground); - --color-destructive: var(--destructive); - --color-destructive-foreground: var(--destructive-foreground); - --color-border: var(--border); - --color-input: var(--input); - --color-ring: var(--ring); - --color-chart-1: var(--chart-1); - --color-chart-2: var(--chart-2); - --color-chart-3: var(--chart-3); - --color-chart-4: var(--chart-4); - --color-chart-5: var(--chart-5); - --color-sidebar: var(--sidebar); - --color-sidebar-foreground: var(--sidebar-foreground); - --color-sidebar-primary: var(--sidebar-primary); - --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); - --color-sidebar-accent: var(--sidebar-accent); - --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); - --color-sidebar-border: var(--sidebar-border); - --color-sidebar-ring: var(--sidebar-ring); +/* ===== GLOBAL OVERRIDES ===== */ - --font-sans: var(--font-sans); - --font-mono: var(--font-mono); - --font-serif: var(--font-serif); - - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - - --shadow-2xs: var(--shadow-2xs); - --shadow-xs: var(--shadow-xs); - --shadow-sm: var(--shadow-sm); - --shadow: var(--shadow); - --shadow-md: var(--shadow-md); - --shadow-lg: var(--shadow-lg); - --shadow-xl: var(--shadow-xl); - --shadow-2xl: var(--shadow-2xl); +* { + box-shadow: none !important; +} + +/* ===== CARDS ===== */ +.neo-card, +[class*="neo-card"] { + border: 1px solid var(--color-neo-border) !important; + box-shadow: none !important; + transform: none !important; + border-radius: var(--radius-neo-lg) !important; + background-color: var(--color-neo-card) !important; +} + +.neo-card:hover, +[class*="neo-card"]:hover { + transform: none !important; + box-shadow: none !important; +} + +/* ===== BUTTONS ===== */ +.neo-btn, +[class*="neo-btn"], +button { + border-width: 1px !important; + box-shadow: none !important; + text-transform: none !important; + font-weight: 500 !important; + transform: none !important; + border-radius: var(--radius-neo-lg) !important; + font-family: var(--font-neo-sans) !important; +} + +.neo-btn:hover, +[class*="neo-btn"]:hover, +button:hover { + transform: none !important; + box-shadow: none !important; +} + +.neo-btn:active, +[class*="neo-btn"]:active { + transform: none !important; +} + +/* Primary button */ +.neo-btn-primary { + background-color: var(--color-neo-accent) !important; + border-color: var(--color-neo-accent) !important; + color: white !important; +} + +/* Success button - use accent blue instead of green */ +.neo-btn-success { + background-color: var(--color-neo-accent) !important; + border-color: var(--color-neo-accent) !important; + color: white !important; +} + +/* Danger button - subtle red */ +.neo-btn-danger { + background-color: var(--color-neo-danger) !important; + border-color: var(--color-neo-danger) !important; + color: white !important; +} + +/* ===== INPUTS ===== */ +.neo-input, +.neo-textarea, +input, +textarea, +select { + border: 1px solid var(--color-neo-border) !important; + box-shadow: none !important; + border-radius: var(--radius-neo-md) !important; + background-color: var(--color-neo-neutral-50) !important; +} + +.neo-input:focus, +.neo-textarea:focus, +input:focus, +textarea:focus, +select:focus { + box-shadow: none !important; + border-color: var(--color-neo-accent) !important; + outline: none !important; +} + +/* ===== BADGES ===== */ +.neo-badge, +[class*="neo-badge"] { + border: 1px solid var(--color-neo-border) !important; + box-shadow: none !important; + border-radius: var(--radius-neo-lg) !important; + font-weight: 500 !important; + text-transform: none !important; +} + +/* ===== PROGRESS BAR ===== */ +.neo-progress { + border: none !important; + box-shadow: none !important; + border-radius: var(--radius-neo-lg) !important; + background-color: var(--color-neo-neutral-100) !important; + overflow: hidden !important; + height: 0.75rem !important; +} + +.neo-progress-fill { + background-color: var(--color-neo-accent) !important; + border-radius: var(--radius-neo-lg) !important; +} + +.neo-progress-fill::after { + display: none !important; +} + +/* ===== KANBAN COLUMNS ===== */ +.kanban-column { + border: 1px solid var(--color-neo-border) !important; + border-radius: var(--radius-neo-lg) !important; + overflow: hidden; + background-color: var(--color-neo-bg) !important; + border-left: none !important; +} + +/* Left accent border on the whole column */ +.kanban-column.kanban-header-pending { + border-left: 3px solid var(--color-neo-accent) !important; +} + +.kanban-column.kanban-header-progress { + border-left: 3px solid var(--color-neo-accent) !important; +} + +.kanban-column.kanban-header-done { + border-left: 3px solid var(--color-neo-accent) !important; +} + +.kanban-header { + background-color: var(--color-neo-card) !important; + border-bottom: 1px solid var(--color-neo-border) !important; + border-left: none !important; +} + +/* ===== MODALS & DROPDOWNS ===== */ +.neo-modal, +[class*="neo-modal"], +[role="dialog"] { + border: 1px solid var(--color-neo-border) !important; + border-radius: var(--radius-neo-xl) !important; + box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.1) !important; +} + +.neo-dropdown, +[class*="dropdown"], +[role="menu"], +[data-radix-popper-content-wrapper] { + border: 1px solid var(--color-neo-border) !important; + border-radius: var(--radius-neo-lg) !important; + box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.08) !important; +} + +/* ===== STATUS BADGES ===== */ +[class*="bg-neo-pending"], +.bg-\[var\(--color-neo-pending\)\] { + background-color: var(--color-neo-neutral-100) !important; + color: var(--color-neo-text-secondary) !important; +} + +[class*="bg-neo-progress"], +.bg-\[var\(--color-neo-progress\)\] { + background-color: oklch(0.9392 0.0166 250.8453) !important; + color: var(--color-neo-accent) !important; +} + +[class*="bg-neo-done"], +.bg-\[var\(--color-neo-done\)\] { + background-color: oklch(0.9392 0.0166 250.8453) !important; + color: var(--color-neo-accent) !important; +} + +/* ===== REMOVE NEO EFFECTS ===== */ +[class*="shadow-neo"], +[class*="shadow-"] { + box-shadow: none !important; +} + +[class*="hover:translate"], +[class*="hover:-translate"], +[class*="translate-x"], +[class*="translate-y"] { + transform: none !important; +} + +/* ===== TEXT STYLING ===== */ +h1, h2, h3, h4, h5, h6, +[class*="heading"], +[class*="title"], +[class*="font-display"] { + text-transform: none !important; + font-family: var(--font-neo-sans) !important; +} + +.uppercase { + text-transform: none !important; +} + +strong, b, +[class*="font-bold"], +[class*="font-black"] { + font-weight: 600 !important; +} + +/* ===== SPECIFIC ELEMENT FIXES ===== */ + +/* Green badges should use accent color */ +[class*="bg-green"], +[class*="bg-emerald"], +[class*="bg-lime"] { + background-color: oklch(0.9392 0.0166 250.8453) !important; + color: var(--color-neo-accent) !important; +} + +/* Category badges */ +[class*="FUNCTIONAL"], +[class*="functional"] { + background-color: oklch(0.9392 0.0166 250.8453) !important; + color: var(--color-neo-accent) !important; +} + +/* Live/Status indicators - use accent instead of green */ +.text-\[var\(--color-neo-done\)\] { + color: var(--color-neo-accent) !important; +} + +/* Override any remaining borders to be thin */ +[class*="border-3"], +[class*="border-b-3"] { + border-width: 1px !important; +} + +/* ===== DARK MODE SPECIFIC FIXES ===== */ + +.dark .neo-card, +.dark [class*="neo-card"] { + background-color: var(--color-neo-card) !important; + border-color: var(--color-neo-border) !important; +} + +.dark .kanban-column { + background-color: var(--color-neo-card) !important; +} + +.dark .kanban-header { + background-color: var(--color-neo-neutral-50) !important; +} + +/* Feature cards in dark mode */ +.dark .neo-card .neo-card { + background-color: var(--color-neo-neutral-50) !important; +} + +/* Badges in dark mode - lighter background for visibility */ +.dark .neo-badge, +.dark [class*="neo-badge"] { + background-color: var(--color-neo-neutral-100) !important; + color: var(--color-neo-text) !important; + border-color: var(--color-neo-border) !important; +} + +/* Status badges in dark mode */ +.dark [class*="bg-neo-done"], +.dark .bg-\[var\(--color-neo-done\)\] { + background-color: oklch(0.25 0.05 245) !important; + color: var(--color-neo-accent) !important; +} + +.dark [class*="bg-neo-progress"], +.dark .bg-\[var\(--color-neo-progress\)\] { + background-color: oklch(0.25 0.05 245) !important; + color: var(--color-neo-accent) !important; +} + +/* Green badges in dark mode */ +.dark [class*="bg-green"], +.dark [class*="bg-emerald"], +.dark [class*="bg-lime"] { + background-color: oklch(0.25 0.05 245) !important; + color: var(--color-neo-accent) !important; +} + +/* Category badges in dark mode */ +.dark [class*="FUNCTIONAL"], +.dark [class*="functional"] { + background-color: oklch(0.25 0.05 245) !important; + color: var(--color-neo-accent) !important; +} + +/* Buttons in dark mode - better visibility */ +.dark .neo-btn, +.dark button { + border-color: var(--color-neo-border) !important; +} + +.dark .neo-btn-primary, +.dark .neo-btn-success { + background-color: var(--color-neo-accent) !important; + border-color: var(--color-neo-accent) !important; + color: white !important; +} + +/* Toggle buttons - fix "Graph" visibility */ +.dark [class*="text-neo-text"] { + color: var(--color-neo-text) !important; +} + +/* Inputs in dark mode */ +.dark input, +.dark textarea, +.dark select { + background-color: var(--color-neo-neutral-50) !important; + border-color: var(--color-neo-border) !important; + color: var(--color-neo-text) !important; } From 8e23fee0944ca0820b4499077a11ff58be89edc8 Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 08:01:30 +0100 Subject: [PATCH 05/63] fix: Prevent mock data implementations with infrastructure features Problem: The coding agent can implement in-memory storage (e.g., `dev-store.ts` with `globalThis`) instead of a real database. These implementations pass all tests because data persists during runtime, but data is lost on server restart. This is a root cause for #68 - agent "passes" features that don't actually work. Solution: 1. Add 5 mandatory Infrastructure Features (indices 0-4) that run first: - Feature 0: Database connection established - Feature 1: Database schema applied correctly - Feature 2: Data persists across server restart (CRITICAL) - Feature 3: No mock data patterns in codebase - Feature 4: Backend API queries real database 2. Add STEP 5.7: Server Restart Persistence Test to coding prompt: - Create test data, stop server, restart, verify data still exists 3. Extend grep patterns for mock detection in STEP 5.6: - globalThis., devStore, dev-store, mockData, fakeData - TODO.*real, STUB, MOCK, new Map() as data stores Changes: - .claude/templates/initializer_prompt.template.md - Infrastructure features - .claude/templates/coding_prompt.template.md - STEP 5.6/5.7 enhancements - .claude/commands/create-spec.md - Phase 3b database question Backwards Compatible: - Works with YOLO mode (uses bash/grep, not browser automation) - Stateless apps can skip database features via create-spec question Co-Authored-By: Claude Opus 4.5 --- .claude/commands/create-spec.md | 43 +++++- .claude/templates/coding_prompt.template.md | 73 ++++++++- .../templates/initializer_prompt.template.md | 139 +++++++++++++++--- 3 files changed, 223 insertions(+), 32 deletions(-) diff --git a/.claude/commands/create-spec.md b/.claude/commands/create-spec.md index f8cae28..f8a1b96 100644 --- a/.claude/commands/create-spec.md +++ b/.claude/commands/create-spec.md @@ -95,6 +95,27 @@ Ask the user about their involvement preference: **For Detailed Mode users**, ask specific tech questions about frontend, backend, database, etc. +### Phase 3b: Database Requirements (MANDATORY) + +**Always ask this question regardless of mode:** + +> "One foundational question about data storage: +> +> **Does this application need to store user data persistently?** +> +> 1. **Yes, needs a database** - Users create, save, and retrieve data (most apps) +> 2. **No, stateless** - Pure frontend, no data storage needed (calculators, static sites) +> 3. **Not sure** - Let me describe what I need and you decide" + +**Branching logic:** + +- **If "Yes" or "Not sure"**: Continue normally. The spec will include database in tech stack and the initializer will create 5 mandatory Infrastructure features (indices 0-4) to verify database connectivity and persistence. + +- **If "No, stateless"**: Note this in the spec. Skip database from tech stack. Infrastructure features will be simplified (no database persistence tests). Mark this clearly: + ```xml + none - stateless application + ``` + ## Phase 4: Features (THE MAIN PHASE) This is where you spend most of your time. Ask questions in plain language that anyone can answer. @@ -207,12 +228,23 @@ After gathering all features, **you** (the agent) should tally up the testable f **Typical ranges for reference:** -- **Simple apps** (todo list, calculator, notes): ~20-50 features -- **Medium apps** (blog, task manager with auth): ~100 features -- **Advanced apps** (e-commerce, CRM, full SaaS): ~150-200 features +- **Simple apps** (todo list, calculator, notes): ~25-55 features (includes 5 infrastructure) +- **Medium apps** (blog, task manager with auth): ~105 features (includes 5 infrastructure) +- **Advanced apps** (e-commerce, CRM, full SaaS): ~155-205 features (includes 5 infrastructure) These are just reference points - your actual count should come from the requirements discussed. +**MANDATORY: Infrastructure Features** + +If the app requires a database (Phase 3b answer was "Yes" or "Not sure"), you MUST include 5 Infrastructure features (indices 0-4): +1. Database connection established +2. Database schema applied correctly +3. Data persists across server restart +4. No mock data patterns in codebase +5. Backend API queries real database + +These features ensure the coding agent implements a real database, not mock data or in-memory storage. + **How to count features:** For each feature area discussed, estimate the number of discrete, testable behaviors: @@ -225,17 +257,20 @@ For each feature area discussed, estimate the number of discrete, testable behav > "Based on what we discussed, here's my feature breakdown: > +> - **Infrastructure (required)**: 5 features (database setup, persistence verification) > - [Category 1]: ~X features > - [Category 2]: ~Y features > - [Category 3]: ~Z features > - ... > -> **Total: ~N features** +> **Total: ~N features** (including 5 infrastructure) > > Does this seem right, or should I adjust?" Let the user confirm or adjust. This becomes your `feature_count` for the spec. +**Important:** The first 5 features (indices 0-4) created by the initializer MUST be the Infrastructure category with no dependencies. All other features depend on these. + ## Phase 5: Technical Details (DERIVED OR DISCUSSED) **For Quick Mode users:** diff --git a/.claude/templates/coding_prompt.template.md b/.claude/templates/coding_prompt.template.md index bce9a14..5d3ecb2 100644 --- a/.claude/templates/coding_prompt.template.md +++ b/.claude/templates/coding_prompt.template.md @@ -156,6 +156,9 @@ Use browser automation tools: - [ ] Deleted the test data - verified it's gone everywhere - [ ] NO unexplained data appeared (would indicate mock data) - [ ] Dashboard/counts reflect real numbers after my changes +- [ ] **Ran extended mock data grep (STEP 5.6) - no hits in src/ (excluding tests)** +- [ ] **Verified no globalThis, devStore, or dev-store patterns** +- [ ] **Server restart test passed (STEP 5.7) - data persists across restart** #### Navigation Verification @@ -174,10 +177,72 @@ Use browser automation tools: ### STEP 5.6: MOCK DATA DETECTION (Before marking passing) -1. **Search code:** `grep -r "mockData\|fakeData\|TODO\|STUB" --include="*.ts" --include="*.tsx"` -2. **Runtime test:** Create unique data (e.g., "TEST_12345") → verify in UI → delete → verify gone -3. **Check database:** All displayed data must come from real DB queries -4. If unexplained data appears, it's mock data - fix before marking passing. +**Run ALL these grep checks. Any hits in src/ (excluding test files) require investigation:** + +```bash +# 1. In-memory storage patterns (CRITICAL - catches dev-store) +grep -r "globalThis\." --include="*.ts" --include="*.tsx" --include="*.js" src/ +grep -r "dev-store\|devStore\|DevStore\|mock-db\|mockDb" --include="*.ts" --include="*.tsx" src/ + +# 2. Mock data variables +grep -r "mockData\|fakeData\|sampleData\|dummyData\|testData" --include="*.ts" --include="*.tsx" src/ + +# 3. TODO/incomplete markers +grep -r "TODO.*real\|TODO.*database\|TODO.*API\|STUB\|MOCK" --include="*.ts" --include="*.tsx" src/ + +# 4. Development-only conditionals +grep -r "isDevelopment\|isDev\|process\.env\.NODE_ENV.*development" --include="*.ts" --include="*.tsx" src/ + +# 5. In-memory collections as data stores (check lib/store/data directories) +grep -r "new Map()\|new Set()" --include="*.ts" --include="*.tsx" src/lib/ src/store/ src/data/ 2>/dev/null +``` + +**Rule:** If ANY grep returns results in production code → investigate → FIX before marking passing. + +**Runtime verification:** +1. Create unique data (e.g., "TEST_12345") → verify in UI → delete → verify gone +2. Check database directly - all displayed data must come from real DB queries +3. If unexplained data appears, it's mock data - fix before marking passing. + +### STEP 5.7: SERVER RESTART PERSISTENCE TEST (MANDATORY for data features) + +**When required:** Any feature involving CRUD operations or data persistence. + +**This test is NON-NEGOTIABLE. It catches in-memory storage implementations that pass all other tests.** + +**Steps:** + +1. Create unique test data via UI or API (e.g., item named "RESTART_TEST_12345") +2. Verify data appears in UI and API response + +3. **STOP the server completely:** + ```bash + pkill -f "node" || pkill -f "npm" || pkill -f "next" + sleep 5 + # Verify server is stopped + pgrep -f "node" && echo "ERROR: Server still running!" && exit 1 + ``` + +4. **RESTART the server:** + ```bash + ./init.sh & + sleep 15 # Allow server to fully start + ``` + +5. **Query for test data - it MUST still exist** + - Via UI: Navigate to data location, verify data appears + - Via API: `curl http://localhost:PORT/api/items` - verify data in response + +6. **If data is GONE:** Implementation uses in-memory storage → CRITICAL FAIL + - Search for: `grep -r "globalThis\|devStore\|dev-store" src/` + - You MUST fix the mock data implementation before proceeding + - Replace in-memory storage with real database queries + +7. **Clean up test data** after successful verification + +**Why this test exists:** In-memory stores like `globalThis.devStore` pass all other tests because data persists during a single server run. Only a full server restart reveals this bug. Skipping this step WILL allow dev-store implementations to slip through. + +**YOLO Mode Note:** Even in YOLO mode, this verification is MANDATORY for data features. Use curl instead of browser automation. ### STEP 6: UPDATE FEATURE STATUS (CAREFULLY!) diff --git a/.claude/templates/initializer_prompt.template.md b/.claude/templates/initializer_prompt.template.md index c6ee081..4594169 100644 --- a/.claude/templates/initializer_prompt.template.md +++ b/.claude/templates/initializer_prompt.template.md @@ -36,9 +36,9 @@ Use the feature_create_bulk tool to add all features at once. You can create fea - Feature count must match the `feature_count` specified in app_spec.txt - Reference tiers for other projects: - - **Simple apps**: ~150 tests - - **Medium apps**: ~250 tests - - **Complex apps**: ~400+ tests + - **Simple apps**: ~155 tests (includes 5 infrastructure) + - **Medium apps**: ~255 tests (includes 5 infrastructure) + - **Complex apps**: ~405+ tests (includes 5 infrastructure) - Both "functional" and "style" categories - Mix of narrow tests (2-5 steps) and comprehensive tests (10+ steps) - At least 25 tests MUST have 10+ steps each (more for complex apps) @@ -60,8 +60,9 @@ Dependencies enable **parallel execution** of independent features. When specifi 2. **Can only depend on EARLIER features** (index must be less than current position) 3. **No circular dependencies** allowed 4. **Maximum 20 dependencies** per feature -5. **Foundation features (index 0-9)** should have NO dependencies -6. **60% of features after index 10** should have at least one dependency +5. **Infrastructure features (indices 0-4)** have NO dependencies - they run FIRST +6. **ALL features after index 4** MUST depend on `[0, 1, 2, 3, 4]` (infrastructure) +7. **60% of features after index 10** should have additional dependencies beyond infrastructure ### Dependency Types @@ -82,30 +83,107 @@ Create WIDE dependency graphs, not linear chains: ```json [ - // FOUNDATION TIER (indices 0-2, no dependencies) - run first - { "name": "App loads without errors", "category": "functional" }, - { "name": "Navigation bar displays", "category": "style" }, - { "name": "Homepage renders correctly", "category": "functional" }, + // INFRASTRUCTURE TIER (indices 0-4, no dependencies) - MUST run first + { "name": "Database connection established", "category": "functional" }, + { "name": "Database schema applied correctly", "category": "functional" }, + { "name": "Data persists across server restart", "category": "functional" }, + { "name": "No mock data patterns in codebase", "category": "functional" }, + { "name": "Backend API queries real database", "category": "functional" }, - // AUTH TIER (indices 3-5, depend on foundation) - run in parallel - { "name": "User can register", "depends_on_indices": [0] }, - { "name": "User can login", "depends_on_indices": [0, 3] }, - { "name": "User can logout", "depends_on_indices": [4] }, + // FOUNDATION TIER (indices 5-7, depend on infrastructure) + { "name": "App loads without errors", "category": "functional", "depends_on_indices": [0, 1, 2, 3, 4] }, + { "name": "Navigation bar displays", "category": "style", "depends_on_indices": [0, 1, 2, 3, 4] }, + { "name": "Homepage renders correctly", "category": "functional", "depends_on_indices": [0, 1, 2, 3, 4] }, - // CORE CRUD TIER (indices 6-9) - WIDE GRAPH: all 4 depend on login - // All 4 start as soon as login passes! - { "name": "User can create todo", "depends_on_indices": [4] }, - { "name": "User can view todos", "depends_on_indices": [4] }, - { "name": "User can edit todo", "depends_on_indices": [4, 6] }, - { "name": "User can delete todo", "depends_on_indices": [4, 6] }, + // AUTH TIER (indices 8-10, depend on foundation + infrastructure) + { "name": "User can register", "depends_on_indices": [0, 1, 2, 3, 4, 5] }, + { "name": "User can login", "depends_on_indices": [0, 1, 2, 3, 4, 5, 8] }, + { "name": "User can logout", "depends_on_indices": [9] }, - // ADVANCED TIER (indices 10-11) - both depend on view, not each other - { "name": "User can filter todos", "depends_on_indices": [7] }, - { "name": "User can search todos", "depends_on_indices": [7] } + // CORE CRUD TIER (indices 11-14) - WIDE GRAPH: all 4 depend on login + { "name": "User can create todo", "depends_on_indices": [9] }, + { "name": "User can view todos", "depends_on_indices": [9] }, + { "name": "User can edit todo", "depends_on_indices": [9, 11] }, + { "name": "User can delete todo", "depends_on_indices": [9, 11] }, + + // ADVANCED TIER (indices 15-16) - both depend on view, not each other + { "name": "User can filter todos", "depends_on_indices": [12] }, + { "name": "User can search todos", "depends_on_indices": [12] } ] ``` -**Result:** With 3 parallel agents, this 12-feature project completes in ~5-6 cycles instead of 12 sequential cycles. +**Result:** With 3 parallel agents, this project completes efficiently with proper database validation first. + +--- + +## MANDATORY INFRASTRUCTURE FEATURES (Indices 0-4) + +**CRITICAL:** Create these FIRST, before any functional features. These features ensure the application uses a real database, not mock data or in-memory storage. + +| Index | Name | Test Steps | +|-------|------|------------| +| 0 | Database connection established | Start server → check logs for DB connection → health endpoint returns DB status | +| 1 | Database schema applied correctly | Connect to DB directly → list tables → verify schema matches spec | +| 2 | Data persists across server restart | Create via API → STOP server completely → START server → query API → data still exists | +| 3 | No mock data patterns in codebase | Run grep for prohibited patterns → must return empty | +| 4 | Backend API queries real database | Check server logs → SQL/DB queries appear for API calls | + +**ALL other features MUST depend on indices [0, 1, 2, 3, 4].** + +### Infrastructure Feature Descriptions + +**Feature 0 - Database connection established:** +``` +Steps: +1. Start the development server +2. Check server logs for database connection message +3. Call health endpoint (e.g., GET /api/health) +4. Verify response includes database status: connected +``` + +**Feature 1 - Database schema applied correctly:** +``` +Steps: +1. Connect to database directly (sqlite3, psql, etc.) +2. List all tables in the database +3. Verify tables match what's defined in app_spec.txt +4. Verify key columns exist on each table +``` + +**Feature 2 - Data persists across server restart (CRITICAL):** +``` +Steps: +1. Create unique test data via API (e.g., POST /api/items with name "RESTART_TEST_12345") +2. Verify data appears in API response (GET /api/items) +3. STOP the server completely: pkill -f "node" && sleep 5 +4. Verify server is stopped: pgrep -f "node" returns nothing +5. RESTART the server: ./init.sh & sleep 15 +6. Query API again: GET /api/items +7. Verify "RESTART_TEST_12345" still exists +8. If data is GONE → CRITICAL FAILURE (in-memory storage detected) +9. Clean up test data +``` + +**Feature 3 - No mock data patterns in codebase:** +``` +Steps: +1. Run: grep -r "globalThis\." --include="*.ts" --include="*.tsx" src/ +2. Run: grep -r "dev-store\|devStore\|DevStore\|mock-db\|mockDb" --include="*.ts" src/ +3. Run: grep -r "mockData\|fakeData\|sampleData\|dummyData" --include="*.ts" src/ +4. Run: grep -r "new Map()\|new Set()" --include="*.ts" src/lib/ src/store/ src/data/ +5. ALL grep commands must return empty (exit code 1) +6. If any returns results → investigate and fix before passing +``` + +**Feature 4 - Backend API queries real database:** +``` +Steps: +1. Start server with verbose logging +2. Make API call (e.g., GET /api/items) +3. Check server logs +4. Verify SQL query appears (SELECT, INSERT, etc.) or ORM query log +5. If no DB queries in logs → implementation is using mock data +``` --- @@ -117,6 +195,7 @@ The feature_list.json **MUST** include tests from ALL 20 categories. Minimum cou | Category | Simple | Medium | Complex | | -------------------------------- | ------- | ------- | -------- | +| **0. Infrastructure (REQUIRED)** | 5 | 5 | 5 | | A. Security & Access Control | 5 | 20 | 40 | | B. Navigation Integrity | 15 | 25 | 40 | | C. Real Data Verification | 20 | 30 | 50 | @@ -137,12 +216,14 @@ The feature_list.json **MUST** include tests from ALL 20 categories. Minimum cou | R. Concurrency & Race Conditions | 5 | 8 | 15 | | S. Export/Import | 5 | 6 | 10 | | T. Performance | 5 | 5 | 10 | -| **TOTAL** | **150** | **250** | **400+** | +| **TOTAL** | **155** | **255** | **405+** | --- ### Category Descriptions +**0. Infrastructure (REQUIRED - Priority 0)** - Database connectivity, schema existence, data persistence across server restart, absence of mock patterns. These features MUST pass before any functional features can begin. All tiers require exactly 5 infrastructure features (indices 0-4). + **A. Security & Access Control** - Test unauthorized access blocking, permission enforcement, session management, role-based access, and data isolation between users. **B. Navigation Integrity** - Test all buttons, links, menus, breadcrumbs, deep links, back button behavior, 404 handling, and post-login/logout redirects. @@ -205,6 +286,16 @@ The feature_list.json must include tests that **actively verify real data** and - `setTimeout` simulating API delays with static data - Static returns instead of database queries +**Additional prohibited patterns (in-memory stores):** + +- `globalThis.` (in-memory storage pattern) +- `dev-store`, `devStore`, `DevStore` (development stores) +- `json-server`, `mirage`, `msw` (mock backends) +- `Map()` or `Set()` used as primary data store +- Environment checks like `if (process.env.NODE_ENV === 'development')` for data routing + +**Why this matters:** In-memory stores (like `globalThis.devStore`) will pass simple tests because data persists during a single server run. But data is LOST on server restart, which is unacceptable for production. The Infrastructure features (0-4) specifically test for this by requiring data to survive a full server restart. + --- **CRITICAL INSTRUCTION:** From 43c37c52fe21868d27a131b98788838730ff934c Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 08:04:14 +0100 Subject: [PATCH 06/63] fix: Pydantic datetime serialization for API endpoints Problem: Several API endpoints return 500 Internal Server Error because datetime objects are not serializable by Pydantic. The error occurs when: - GET /agent/{project}/status - GET /devserver/{project}/status - GET /schedules/{project}/next Root cause: Pydantic models expect strings for Optional datetime fields, but the code was passing raw datetime objects. Solution: Convert datetime objects to ISO 8601 strings using .isoformat() before returning in Pydantic response models. Changes: - server/routers/agent.py: Fix started_at serialization - server/routers/devserver.py: Fix started_at serialization - server/routers/schedules.py: Fix next_start/next_end serialization Co-Authored-By: Claude Opus 4.5 --- server/routers/agent.py | 2 +- server/routers/devserver.py | 2 +- server/routers/schedules.py | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/server/routers/agent.py b/server/routers/agent.py index 309024d..422f86b 100644 --- a/server/routers/agent.py +++ b/server/routers/agent.py @@ -93,7 +93,7 @@ async def get_agent_status(project_name: str): return AgentStatus( status=manager.status, pid=manager.pid, - started_at=manager.started_at, + started_at=manager.started_at.isoformat() if manager.started_at else None, yolo_mode=manager.yolo_mode, model=manager.model, parallel_mode=manager.parallel_mode, diff --git a/server/routers/devserver.py b/server/routers/devserver.py index 673bc3e..18f91ec 100644 --- a/server/routers/devserver.py +++ b/server/routers/devserver.py @@ -129,7 +129,7 @@ async def get_devserver_status(project_name: str) -> DevServerStatus: pid=manager.pid, url=manager.detected_url, command=manager._command, - started_at=manager.started_at, + started_at=manager.started_at.isoformat() if manager.started_at else None, ) diff --git a/server/routers/schedules.py b/server/routers/schedules.py index 7c6c4ed..2a11ba3 100644 --- a/server/routers/schedules.py +++ b/server/routers/schedules.py @@ -256,8 +256,8 @@ async def get_next_scheduled_run(project_name: str): return NextRunResponse( has_schedules=True, - next_start=next_start if active_count == 0 else None, - next_end=latest_end, + next_start=next_start.isoformat() if (active_count == 0 and next_start) else None, + next_end=latest_end.isoformat() if latest_end else None, is_currently_running=active_count > 0, active_schedule_count=active_count, ) From 84843459b4f8916caf8d8df15f546d4adcce7d71 Mon Sep 17 00:00:00 2001 From: nioasoft Date: Sun, 25 Jan 2026 09:36:48 +0200 Subject: [PATCH 07/63] fix: add keyboard accessibility and improve env var validation Add focus-visible styles for keyboard navigation accessibility and improve PLAYWRIGHT_HEADLESS environment variable validation to warn users about invalid values instead of silently defaulting. Co-Authored-By: Claude Opus 4.5 --- client.py | 10 +++++++--- ui/src/styles/custom-theme.css | 26 ++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/client.py b/client.py index 4bf3669..2311445 100644 --- a/client.py +++ b/client.py @@ -50,9 +50,13 @@ def get_playwright_headless() -> bool: Reads from PLAYWRIGHT_HEADLESS environment variable, defaults to True. Returns True for headless mode (invisible browser), False for visible browser. """ - value = os.getenv("PLAYWRIGHT_HEADLESS", str(DEFAULT_PLAYWRIGHT_HEADLESS).lower()).lower() - # Accept various truthy/falsy values - return value in ("true", "1", "yes", "on") + value = os.getenv("PLAYWRIGHT_HEADLESS", str(DEFAULT_PLAYWRIGHT_HEADLESS).lower()).strip().lower() + truthy = {"true", "1", "yes", "on"} + falsy = {"false", "0", "no", "off"} + if value not in truthy | falsy: + print(f" - Warning: Invalid PLAYWRIGHT_HEADLESS='{value}', defaulting to {DEFAULT_PLAYWRIGHT_HEADLESS}") + return DEFAULT_PLAYWRIGHT_HEADLESS + return value in truthy def get_playwright_browser() -> str: diff --git a/ui/src/styles/custom-theme.css b/ui/src/styles/custom-theme.css index 69748ba..1d7a032 100644 --- a/ui/src/styles/custom-theme.css +++ b/ui/src/styles/custom-theme.css @@ -172,6 +172,32 @@ select:focus { outline: none !important; } +/* ===== KEYBOARD ACCESSIBILITY ===== */ +/* Focus-visible styles for keyboard navigation */ +.neo-btn:focus-visible, +[class*="neo-btn"]:focus-visible, +button:focus-visible { + outline: 2px solid var(--color-neo-accent) !important; + outline-offset: 2px !important; +} + +.neo-input:focus-visible, +.neo-textarea:focus-visible, +input:focus-visible, +textarea:focus-visible, +select:focus-visible { + outline: 2px solid var(--color-neo-accent) !important; + outline-offset: 2px !important; + border-color: var(--color-neo-accent) !important; +} + +a:focus-visible, +[role="button"]:focus-visible, +[tabindex]:focus-visible { + outline: 2px solid var(--color-neo-accent) !important; + outline-offset: 2px !important; +} + /* ===== BADGES ===== */ .neo-badge, [class*="neo-badge"] { From dae16c3cca64dc2edd234eff61c23ed65cc1d671 Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 11:43:54 +0100 Subject: [PATCH 08/63] fix: Address CodeRabbit review feedback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Fix math error in category totals (155→165, 255→265) - Fix example JSON to include [0,1,2,3,4] dependencies for all features - Add more robust server shutdown (SIGTERM then SIGKILL) - Add health check after server restart - Align grep patterns between templates (add .js, testData, TODO/STUB/MOCK) - Add package.json check for mock backend libraries - Reference STEP 5.6 instead of duplicating grep commands Co-Authored-By: Claude Opus 4.5 --- .claude/templates/coding_prompt.template.md | 16 ++++++--- .../templates/initializer_prompt.template.md | 35 ++++++++++--------- 2 files changed, 31 insertions(+), 20 deletions(-) diff --git a/.claude/templates/coding_prompt.template.md b/.claude/templates/coding_prompt.template.md index 5d3ecb2..45af425 100644 --- a/.claude/templates/coding_prompt.template.md +++ b/.claude/templates/coding_prompt.template.md @@ -217,16 +217,24 @@ grep -r "new Map()\|new Set()" --include="*.ts" --include="*.tsx" src/lib/ src/s 3. **STOP the server completely:** ```bash + # Send SIGTERM first, then SIGKILL if needed pkill -f "node" || pkill -f "npm" || pkill -f "next" - sleep 5 + sleep 3 + pkill -9 -f "node" 2>/dev/null || true + sleep 2 # Verify server is stopped - pgrep -f "node" && echo "ERROR: Server still running!" && exit 1 + if pgrep -f "node" > /dev/null; then + echo "ERROR: Server still running!" + exit 1 + fi ``` 4. **RESTART the server:** ```bash ./init.sh & sleep 15 # Allow server to fully start + # Verify server is responding + curl -f http://localhost:3000/api/health || curl -f http://localhost:3000 || echo "WARNING: Health check failed" ``` 5. **Query for test data - it MUST still exist** @@ -234,8 +242,8 @@ grep -r "new Map()\|new Set()" --include="*.ts" --include="*.tsx" src/lib/ src/s - Via API: `curl http://localhost:PORT/api/items` - verify data in response 6. **If data is GONE:** Implementation uses in-memory storage → CRITICAL FAIL - - Search for: `grep -r "globalThis\|devStore\|dev-store" src/` - - You MUST fix the mock data implementation before proceeding + - Run all grep commands from STEP 5.6 to identify the mock pattern + - You MUST fix the in-memory storage implementation before proceeding - Replace in-memory storage with real database queries 7. **Clean up test data** after successful verification diff --git a/.claude/templates/initializer_prompt.template.md b/.claude/templates/initializer_prompt.template.md index 4594169..44140b6 100644 --- a/.claude/templates/initializer_prompt.template.md +++ b/.claude/templates/initializer_prompt.template.md @@ -36,8 +36,8 @@ Use the feature_create_bulk tool to add all features at once. You can create fea - Feature count must match the `feature_count` specified in app_spec.txt - Reference tiers for other projects: - - **Simple apps**: ~155 tests (includes 5 infrastructure) - - **Medium apps**: ~255 tests (includes 5 infrastructure) + - **Simple apps**: ~165 tests (includes 5 infrastructure) + - **Medium apps**: ~265 tests (includes 5 infrastructure) - **Complex apps**: ~405+ tests (includes 5 infrastructure) - Both "functional" and "style" categories - Mix of narrow tests (2-5 steps) and comprehensive tests (10+ steps) @@ -98,17 +98,17 @@ Create WIDE dependency graphs, not linear chains: // AUTH TIER (indices 8-10, depend on foundation + infrastructure) { "name": "User can register", "depends_on_indices": [0, 1, 2, 3, 4, 5] }, { "name": "User can login", "depends_on_indices": [0, 1, 2, 3, 4, 5, 8] }, - { "name": "User can logout", "depends_on_indices": [9] }, + { "name": "User can logout", "depends_on_indices": [0, 1, 2, 3, 4, 9] }, // CORE CRUD TIER (indices 11-14) - WIDE GRAPH: all 4 depend on login - { "name": "User can create todo", "depends_on_indices": [9] }, - { "name": "User can view todos", "depends_on_indices": [9] }, - { "name": "User can edit todo", "depends_on_indices": [9, 11] }, - { "name": "User can delete todo", "depends_on_indices": [9, 11] }, + { "name": "User can create todo", "depends_on_indices": [0, 1, 2, 3, 4, 9] }, + { "name": "User can view todos", "depends_on_indices": [0, 1, 2, 3, 4, 9] }, + { "name": "User can edit todo", "depends_on_indices": [0, 1, 2, 3, 4, 9, 11] }, + { "name": "User can delete todo", "depends_on_indices": [0, 1, 2, 3, 4, 9, 11] }, // ADVANCED TIER (indices 15-16) - both depend on view, not each other - { "name": "User can filter todos", "depends_on_indices": [12] }, - { "name": "User can search todos", "depends_on_indices": [12] } + { "name": "User can filter todos", "depends_on_indices": [0, 1, 2, 3, 4, 12] }, + { "name": "User can search todos", "depends_on_indices": [0, 1, 2, 3, 4, 12] } ] ``` @@ -167,12 +167,15 @@ Steps: **Feature 3 - No mock data patterns in codebase:** ``` Steps: -1. Run: grep -r "globalThis\." --include="*.ts" --include="*.tsx" src/ -2. Run: grep -r "dev-store\|devStore\|DevStore\|mock-db\|mockDb" --include="*.ts" src/ -3. Run: grep -r "mockData\|fakeData\|sampleData\|dummyData" --include="*.ts" src/ -4. Run: grep -r "new Map()\|new Set()" --include="*.ts" src/lib/ src/store/ src/data/ -5. ALL grep commands must return empty (exit code 1) -6. If any returns results → investigate and fix before passing +1. Run: grep -r "globalThis\." --include="*.ts" --include="*.tsx" --include="*.js" src/ +2. Run: grep -r "dev-store\|devStore\|DevStore\|mock-db\|mockDb" --include="*.ts" --include="*.tsx" src/ +3. Run: grep -r "mockData\|testData\|fakeData\|sampleData\|dummyData" --include="*.ts" --include="*.tsx" src/ +4. Run: grep -r "TODO.*real\|TODO.*database\|TODO.*API\|STUB\|MOCK" --include="*.ts" --include="*.tsx" src/ +5. Run: grep -r "isDevelopment\|isDev\|process\.env\.NODE_ENV.*development" --include="*.ts" --include="*.tsx" src/ +6. Run: grep -r "new Map()\|new Set()" --include="*.ts" --include="*.tsx" src/lib/ src/store/ src/data/ 2>/dev/null +7. Run: grep -E "json-server|miragejs|msw" package.json +8. ALL grep commands must return empty (exit code 1) +9. If any returns results → investigate and fix before passing ``` **Feature 4 - Backend API queries real database:** @@ -216,7 +219,7 @@ The feature_list.json **MUST** include tests from ALL 20 categories. Minimum cou | R. Concurrency & Race Conditions | 5 | 8 | 15 | | S. Export/Import | 5 | 6 | 10 | | T. Performance | 5 | 5 | 10 | -| **TOTAL** | **155** | **255** | **405+** | +| **TOTAL** | **165** | **265** | **405+** | --- From e7564865154ba9f301fdb68fd5c67ea9606860c1 Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 11:50:35 +0100 Subject: [PATCH 09/63] fix: Address remaining CodeRabbit feedback - Escape parentheses in grep patterns: new Map\(\) and new Set\(\) - Add --include="*.js" to all grep commands for complete coverage Co-Authored-By: Claude Opus 4.5 --- .claude/templates/coding_prompt.template.md | 10 +++++----- .claude/templates/initializer_prompt.template.md | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/.claude/templates/coding_prompt.template.md b/.claude/templates/coding_prompt.template.md index 45af425..4c83ea1 100644 --- a/.claude/templates/coding_prompt.template.md +++ b/.claude/templates/coding_prompt.template.md @@ -182,19 +182,19 @@ Use browser automation tools: ```bash # 1. In-memory storage patterns (CRITICAL - catches dev-store) grep -r "globalThis\." --include="*.ts" --include="*.tsx" --include="*.js" src/ -grep -r "dev-store\|devStore\|DevStore\|mock-db\|mockDb" --include="*.ts" --include="*.tsx" src/ +grep -r "dev-store\|devStore\|DevStore\|mock-db\|mockDb" --include="*.ts" --include="*.tsx" --include="*.js" src/ # 2. Mock data variables -grep -r "mockData\|fakeData\|sampleData\|dummyData\|testData" --include="*.ts" --include="*.tsx" src/ +grep -r "mockData\|fakeData\|sampleData\|dummyData\|testData" --include="*.ts" --include="*.tsx" --include="*.js" src/ # 3. TODO/incomplete markers -grep -r "TODO.*real\|TODO.*database\|TODO.*API\|STUB\|MOCK" --include="*.ts" --include="*.tsx" src/ +grep -r "TODO.*real\|TODO.*database\|TODO.*API\|STUB\|MOCK" --include="*.ts" --include="*.tsx" --include="*.js" src/ # 4. Development-only conditionals -grep -r "isDevelopment\|isDev\|process\.env\.NODE_ENV.*development" --include="*.ts" --include="*.tsx" src/ +grep -r "isDevelopment\|isDev\|process\.env\.NODE_ENV.*development" --include="*.ts" --include="*.tsx" --include="*.js" src/ # 5. In-memory collections as data stores (check lib/store/data directories) -grep -r "new Map()\|new Set()" --include="*.ts" --include="*.tsx" src/lib/ src/store/ src/data/ 2>/dev/null +grep -r "new Map\(\)\|new Set\(\)" --include="*.ts" --include="*.tsx" --include="*.js" src/lib/ src/store/ src/data/ 2>/dev/null ``` **Rule:** If ANY grep returns results in production code → investigate → FIX before marking passing. diff --git a/.claude/templates/initializer_prompt.template.md b/.claude/templates/initializer_prompt.template.md index 44140b6..7d8db3e 100644 --- a/.claude/templates/initializer_prompt.template.md +++ b/.claude/templates/initializer_prompt.template.md @@ -168,11 +168,11 @@ Steps: ``` Steps: 1. Run: grep -r "globalThis\." --include="*.ts" --include="*.tsx" --include="*.js" src/ -2. Run: grep -r "dev-store\|devStore\|DevStore\|mock-db\|mockDb" --include="*.ts" --include="*.tsx" src/ -3. Run: grep -r "mockData\|testData\|fakeData\|sampleData\|dummyData" --include="*.ts" --include="*.tsx" src/ -4. Run: grep -r "TODO.*real\|TODO.*database\|TODO.*API\|STUB\|MOCK" --include="*.ts" --include="*.tsx" src/ -5. Run: grep -r "isDevelopment\|isDev\|process\.env\.NODE_ENV.*development" --include="*.ts" --include="*.tsx" src/ -6. Run: grep -r "new Map()\|new Set()" --include="*.ts" --include="*.tsx" src/lib/ src/store/ src/data/ 2>/dev/null +2. Run: grep -r "dev-store\|devStore\|DevStore\|mock-db\|mockDb" --include="*.ts" --include="*.tsx" --include="*.js" src/ +3. Run: grep -r "mockData\|testData\|fakeData\|sampleData\|dummyData" --include="*.ts" --include="*.tsx" --include="*.js" src/ +4. Run: grep -r "TODO.*real\|TODO.*database\|TODO.*API\|STUB\|MOCK" --include="*.ts" --include="*.tsx" --include="*.js" src/ +5. Run: grep -r "isDevelopment\|isDev\|process\.env\.NODE_ENV.*development" --include="*.ts" --include="*.tsx" --include="*.js" src/ +6. Run: grep -r "new Map\(\)\|new Set\(\)" --include="*.ts" --include="*.tsx" --include="*.js" src/lib/ src/store/ src/data/ 2>/dev/null 7. Run: grep -E "json-server|miragejs|msw" package.json 8. ALL grep commands must return empty (exit code 1) 9. If any returns results → investigate and fix before passing From 6731ef44ea33c0ed8f3b20629f2cca7046f576c1 Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 12:07:36 +0100 Subject: [PATCH 10/63] fix: use consistent priority increment when skipping features (#65) The REST API skip endpoint was using max_priority + 1000, while the MCP server used max_priority + 1. This caused priority inflation where values could reach 10,000+ after multiple skips. Changed to use + 1 for consistency with mcp_server/feature_mcp.py:345. Fixes: leonvanzyl/autocoder#65 Co-Authored-By: Claude Opus 4.5 --- server/routers/features.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/server/routers/features.py b/server/routers/features.py index a830001..e5ad919 100644 --- a/server/routers/features.py +++ b/server/routers/features.py @@ -551,9 +551,9 @@ async def skip_feature(project_name: str, feature_id: int): if not feature: raise HTTPException(status_code=404, detail=f"Feature {feature_id} not found") - # Set priority to max + 1000 to push to end + # Set priority to max + 1 to push to end (consistent with MCP server) max_priority = session.query(Feature).order_by(Feature.priority.desc()).first() - feature.priority = (max_priority.priority if max_priority else 0) + 1000 + feature.priority = (max_priority.priority if max_priority else 0) + 1 session.commit() From 33e9f7b4d0b092fc9bba9026587ebb10f74129be Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 12:07:53 +0100 Subject: [PATCH 11/63] fix: stop spawning testing agents after project completion (#66) When all features pass, the orchestrator continued spawning testing agents for 10+ minutes, wasting tokens on unnecessary regression tests. Added a check for get_all_complete() to prevent this. Fixes: leonvanzyl/autocoder#66 Co-Authored-By: Claude Opus 4.5 --- parallel_orchestrator.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/parallel_orchestrator.py b/parallel_orchestrator.py index d2db637..486b963 100644 --- a/parallel_orchestrator.py +++ b/parallel_orchestrator.py @@ -401,6 +401,10 @@ class ParallelOrchestrator: if passing_count == 0: return + # Don't spawn testing agents if all features are already complete + if self.get_all_complete(): + return + # Spawn testing agents one at a time, re-checking limits each time # This avoids TOCTOU race by holding lock during the decision while True: From dbbc7d5ce582feb9332c7d4f2168f1c8807aae52 Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 12:11:58 +0100 Subject: [PATCH 12/63] feat: allow extending pkill process names via config (#85) Previously, pkill was limited to a hardcoded set of process names (node, npm, npx, vite, next). Users building Python/Ruby/Go apps couldn't kill their dev servers. Changes: - Added pkill_processes config option to org config (~/.autocoder/config.yaml) - Added pkill_processes config option to project config (.autocoder/allowed_commands.yaml) - Modified validate_pkill_command() to accept extra_processes parameter - Added get_effective_pkill_processes() to merge default + org + project processes - Updated bash_security_hook to pass configured processes to validator Example usage: ```yaml # ~/.autocoder/config.yaml version: 1 pkill_processes: - python - uvicorn - gunicorn ``` Fixes: leonvanzyl/autocoder#85 Co-Authored-By: Claude Opus 4.5 --- security.py | 95 ++++++++++++++++++++++++++---- test_security.py | 147 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 231 insertions(+), 11 deletions(-) diff --git a/security.py b/security.py index c15f3dc..195aa92 100644 --- a/security.py +++ b/security.py @@ -219,23 +219,37 @@ def extract_commands(command_string: str) -> list[str]: return commands -def validate_pkill_command(command_string: str) -> tuple[bool, str]: +# Default pkill process names (hardcoded baseline, always available) +DEFAULT_PKILL_PROCESSES = { + "node", + "npm", + "npx", + "vite", + "next", +} + + +def validate_pkill_command( + command_string: str, + extra_processes: Optional[set[str]] = None +) -> tuple[bool, str]: """ Validate pkill commands - only allow killing dev-related processes. Uses shlex to parse the command, avoiding regex bypass vulnerabilities. + Args: + command_string: The pkill command to validate + extra_processes: Optional set of additional process names to allow + (from org/project config pkill_processes) + Returns: Tuple of (is_allowed, reason_if_blocked) """ - # Allowed process names for pkill - allowed_process_names = { - "node", - "npm", - "npx", - "vite", - "next", - } + # Merge default processes with any extra configured processes + allowed_process_names = DEFAULT_PKILL_PROCESSES.copy() + if extra_processes: + allowed_process_names |= extra_processes try: tokens = shlex.split(command_string) @@ -264,7 +278,7 @@ def validate_pkill_command(command_string: str) -> tuple[bool, str]: if target in allowed_process_names: return True, "" - return False, f"pkill only allowed for dev processes: {allowed_process_names}" + return False, f"pkill only allowed for processes: {sorted(allowed_process_names)}" def validate_chmod_command(command_string: str) -> tuple[bool, str]: @@ -455,6 +469,15 @@ def load_org_config() -> Optional[dict]: if not isinstance(cmd, str): return None + # Validate pkill_processes if present + if "pkill_processes" in config: + processes = config["pkill_processes"] + if not isinstance(processes, list): + return None + for proc in processes: + if not isinstance(proc, str) or proc.strip() == "": + return None + return config except (yaml.YAMLError, IOError, OSError): @@ -508,6 +531,15 @@ def load_project_commands(project_dir: Path) -> Optional[dict]: if not isinstance(cmd["name"], str): return None + # Validate pkill_processes if present + if "pkill_processes" in config: + processes = config["pkill_processes"] + if not isinstance(processes, list): + return None + for proc in processes: + if not isinstance(proc, str) or proc.strip() == "": + return None + return config except (yaml.YAMLError, IOError, OSError): @@ -628,6 +660,42 @@ def get_project_allowed_commands(project_dir: Optional[Path]) -> set[str]: return allowed +def get_effective_pkill_processes(project_dir: Optional[Path]) -> set[str]: + """ + Get effective pkill process names after hierarchy resolution. + + Merges processes from: + 1. DEFAULT_PKILL_PROCESSES (hardcoded baseline) + 2. Org config pkill_processes + 3. Project config pkill_processes + + Args: + project_dir: Path to the project directory, or None + + Returns: + Set of allowed process names for pkill + """ + # Start with default processes + processes = DEFAULT_PKILL_PROCESSES.copy() + + # Add org-level pkill_processes + org_config = load_org_config() + if org_config: + org_processes = org_config.get("pkill_processes", []) + if isinstance(org_processes, list): + processes |= {p for p in org_processes if isinstance(p, str) and p.strip()} + + # Add project-level pkill_processes + if project_dir: + project_config = load_project_commands(project_dir) + if project_config: + proj_processes = project_config.get("pkill_processes", []) + if isinstance(proj_processes, list): + processes |= {p for p in proj_processes if isinstance(p, str) and p.strip()} + + return processes + + def is_command_allowed(command: str, allowed_commands: set[str]) -> bool: """ Check if a command is allowed (supports patterns). @@ -692,6 +760,9 @@ async def bash_security_hook(input_data, tool_use_id=None, context=None): # Get effective commands using hierarchy resolution allowed_commands, blocked_commands = get_effective_commands(project_dir) + # Get effective pkill processes (includes org/project config) + pkill_processes = get_effective_pkill_processes(project_dir) + # Split into segments for per-command validation segments = split_command_segments(command) @@ -725,7 +796,9 @@ async def bash_security_hook(input_data, tool_use_id=None, context=None): cmd_segment = command # Fallback to full command if cmd == "pkill": - allowed, reason = validate_pkill_command(cmd_segment) + # Pass configured extra processes (beyond defaults) + extra_procs = pkill_processes - DEFAULT_PKILL_PROCESSES + allowed, reason = validate_pkill_command(cmd_segment, extra_procs if extra_procs else None) if not allowed: return {"decision": "block", "reason": reason} elif cmd == "chmod": diff --git a/test_security.py b/test_security.py index 5b46cfe..5bd1867 100644 --- a/test_security.py +++ b/test_security.py @@ -15,14 +15,17 @@ from contextlib import contextmanager from pathlib import Path from security import ( + DEFAULT_PKILL_PROCESSES, bash_security_hook, extract_commands, get_effective_commands, + get_effective_pkill_processes, load_org_config, load_project_commands, matches_pattern, validate_chmod_command, validate_init_script, + validate_pkill_command, validate_project_command, ) @@ -670,6 +673,145 @@ blocked_commands: return passed, failed +def test_pkill_extensibility(): + """Test that pkill processes can be extended via config.""" + print("\nTesting pkill process extensibility:\n") + passed = 0 + failed = 0 + + # Test 1: Default processes work without config + allowed, reason = validate_pkill_command("pkill node") + if allowed: + print(" PASS: Default process 'node' allowed") + passed += 1 + else: + print(f" FAIL: Default process 'node' should be allowed: {reason}") + failed += 1 + + # Test 2: Non-default process blocked without config + allowed, reason = validate_pkill_command("pkill python") + if not allowed: + print(" PASS: Non-default process 'python' blocked without config") + passed += 1 + else: + print(" FAIL: Non-default process 'python' should be blocked without config") + failed += 1 + + # Test 3: Extra processes allowed when passed + allowed, reason = validate_pkill_command("pkill python", extra_processes={"python"}) + if allowed: + print(" PASS: Extra process 'python' allowed when configured") + passed += 1 + else: + print(f" FAIL: Extra process 'python' should be allowed when configured: {reason}") + failed += 1 + + # Test 4: Default processes still work with extra processes + allowed, reason = validate_pkill_command("pkill npm", extra_processes={"python"}) + if allowed: + print(" PASS: Default process 'npm' still works with extra processes") + passed += 1 + else: + print(f" FAIL: Default process should still work: {reason}") + failed += 1 + + # Test 5: Test get_effective_pkill_processes with org config + with tempfile.TemporaryDirectory() as tmphome: + with tempfile.TemporaryDirectory() as tmpproject: + with temporary_home(tmphome): + org_dir = Path(tmphome) / ".autocoder" + org_dir.mkdir() + org_config_path = org_dir / "config.yaml" + + # Create org config with extra pkill processes + org_config_path.write_text("""version: 1 +pkill_processes: + - python + - uvicorn +""") + + project_dir = Path(tmpproject) + processes = get_effective_pkill_processes(project_dir) + + # Should include defaults + org processes + if "node" in processes and "python" in processes and "uvicorn" in processes: + print(" PASS: Org pkill_processes merged with defaults") + passed += 1 + else: + print(f" FAIL: Expected node, python, uvicorn in {processes}") + failed += 1 + + # Test 6: Test get_effective_pkill_processes with project config + with tempfile.TemporaryDirectory() as tmphome: + with tempfile.TemporaryDirectory() as tmpproject: + with temporary_home(tmphome): + project_dir = Path(tmpproject) + project_autocoder = project_dir / ".autocoder" + project_autocoder.mkdir() + project_config = project_autocoder / "allowed_commands.yaml" + + # Create project config with extra pkill processes + project_config.write_text("""version: 1 +commands: [] +pkill_processes: + - gunicorn + - flask +""") + + processes = get_effective_pkill_processes(project_dir) + + # Should include defaults + project processes + if "node" in processes and "gunicorn" in processes and "flask" in processes: + print(" PASS: Project pkill_processes merged with defaults") + passed += 1 + else: + print(f" FAIL: Expected node, gunicorn, flask in {processes}") + failed += 1 + + # Test 7: Integration test - pkill python blocked by default + with tempfile.TemporaryDirectory() as tmphome: + with tempfile.TemporaryDirectory() as tmpproject: + with temporary_home(tmphome): + project_dir = Path(tmpproject) + input_data = {"tool_name": "Bash", "tool_input": {"command": "pkill python"}} + context = {"project_dir": str(project_dir)} + result = asyncio.run(bash_security_hook(input_data, context=context)) + + if result.get("decision") == "block": + print(" PASS: pkill python blocked without config") + passed += 1 + else: + print(" FAIL: pkill python should be blocked without config") + failed += 1 + + # Test 8: Integration test - pkill python allowed with org config + with tempfile.TemporaryDirectory() as tmphome: + with tempfile.TemporaryDirectory() as tmpproject: + with temporary_home(tmphome): + org_dir = Path(tmphome) / ".autocoder" + org_dir.mkdir() + org_config_path = org_dir / "config.yaml" + + org_config_path.write_text("""version: 1 +pkill_processes: + - python +""") + + project_dir = Path(tmpproject) + input_data = {"tool_name": "Bash", "tool_input": {"command": "pkill python"}} + context = {"project_dir": str(project_dir)} + result = asyncio.run(bash_security_hook(input_data, context=context)) + + if result.get("decision") != "block": + print(" PASS: pkill python allowed with org config") + passed += 1 + else: + print(f" FAIL: pkill python should be allowed with org config: {result}") + failed += 1 + + return passed, failed + + def main(): print("=" * 70) print(" SECURITY HOOK TESTS") @@ -733,6 +875,11 @@ def main(): passed += org_block_passed failed += org_block_failed + # Test pkill process extensibility + pkill_passed, pkill_failed = test_pkill_extensibility() + passed += pkill_passed + failed += pkill_failed + # Commands that SHOULD be blocked print("\nCommands that should be BLOCKED:\n") dangerous = [ From 32c7778ee5583997b9bb2bd8365ff386e784731d Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 12:12:38 +0100 Subject: [PATCH 13/63] fix: prevent cross-project UI contamination (#71) When running multiple projects simultaneously, UI would show mixed data because the manager registry used only project_name as key. Projects with the same name but different paths shared the same manager instance. Changed manager registries to use composite key (project_name, resolved_path): - server/services/process_manager.py: AgentProcessManager registry - server/services/dev_server_manager.py: DevServerProcessManager registry This ensures that: - /old/my-app and /new/my-app get separate managers - Multiple browser tabs viewing different projects stay isolated - Project renames don't cause callback contamination Fixes: leonvanzyl/autocoder#71 Also fixes: leonvanzyl/autocoder#62 (progress bar sync) Also fixes: leonvanzyl/autocoder#61 (features missing in kanban) Co-Authored-By: Claude Opus 4.5 --- server/services/dev_server_manager.py | 21 ++++++++------------- server/services/process_manager.py | 12 ++++++++---- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/server/services/dev_server_manager.py b/server/services/dev_server_manager.py index 063e076..5acfbc8 100644 --- a/server/services/dev_server_manager.py +++ b/server/services/dev_server_manager.py @@ -428,7 +428,9 @@ class DevServerProcessManager: # Global registry of dev server managers per project with thread safety -_managers: dict[str, DevServerProcessManager] = {} +# Key is (project_name, resolved_project_dir) to prevent cross-project contamination +# when different projects share the same name but have different paths +_managers: dict[tuple[str, str], DevServerProcessManager] = {} _managers_lock = threading.Lock() @@ -444,18 +446,11 @@ def get_devserver_manager(project_name: str, project_dir: Path) -> DevServerProc DevServerProcessManager instance for the project """ with _managers_lock: - if project_name in _managers: - manager = _managers[project_name] - # Update project_dir in case project was moved - if manager.project_dir.resolve() != project_dir.resolve(): - logger.info( - f"Project {project_name} path updated: {manager.project_dir} -> {project_dir}" - ) - manager.project_dir = project_dir - manager.lock_file = project_dir / ".devserver.lock" - return manager - _managers[project_name] = DevServerProcessManager(project_name, project_dir) - return _managers[project_name] + # Use composite key to prevent cross-project UI contamination (#71) + key = (project_name, str(project_dir.resolve())) + if key not in _managers: + _managers[key] = DevServerProcessManager(project_name, project_dir) + return _managers[key] async def cleanup_all_devservers() -> None: diff --git a/server/services/process_manager.py b/server/services/process_manager.py index 350905f..692c946 100644 --- a/server/services/process_manager.py +++ b/server/services/process_manager.py @@ -510,7 +510,9 @@ class AgentProcessManager: # Global registry of process managers per project with thread safety -_managers: dict[str, AgentProcessManager] = {} +# Key is (project_name, resolved_project_dir) to prevent cross-project contamination +# when different projects share the same name but have different paths +_managers: dict[tuple[str, str], AgentProcessManager] = {} _managers_lock = threading.Lock() @@ -523,9 +525,11 @@ def get_manager(project_name: str, project_dir: Path, root_dir: Path) -> AgentPr root_dir: Root directory of the autonomous-coding-ui project """ with _managers_lock: - if project_name not in _managers: - _managers[project_name] = AgentProcessManager(project_name, project_dir, root_dir) - return _managers[project_name] + # Use composite key to prevent cross-project UI contamination (#71) + key = (project_name, str(project_dir.resolve())) + if key not in _managers: + _managers[key] = AgentProcessManager(project_name, project_dir, root_dir) + return _managers[key] async def cleanup_all_managers() -> None: From be20c8a3ef4f2019b15c32eeb79f7a3ebb62389d Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 12:14:23 +0100 Subject: [PATCH 14/63] feat: add --host argument for WebUI remote access (#81) Users can now access the WebUI remotely (e.g., via VS Code tunnels, remote servers) by specifying a host address: python start_ui.py --host 0.0.0.0 python start_ui.py --host 0.0.0.0 --port 8888 Changes: - Added --host and --port CLI arguments to start_ui.py - Security warning displayed when remote access is enabled - AUTOCODER_ALLOW_REMOTE env var passed to server - server/main.py conditionally disables localhost middleware - CORS updated to allow all origins when remote access is enabled - Browser auto-open disabled for remote hosts Security considerations documented in warning: - File system access to project directories - API can start/stop agents and modify files - Recommend firewall or VPN for protection Fixes: leonvanzyl/autocoder#81 Co-Authored-By: Claude Opus 4.5 --- server/main.py | 56 +++++++++++++++++++++++-------------- start_ui.py | 75 ++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 90 insertions(+), 41 deletions(-) diff --git a/server/main.py b/server/main.py index f3d3504..1b01f79 100644 --- a/server/main.py +++ b/server/main.py @@ -88,35 +88,49 @@ app = FastAPI( lifespan=lifespan, ) -# CORS - allow only localhost origins for security -app.add_middleware( - CORSMiddleware, - allow_origins=[ - "http://localhost:5173", # Vite dev server - "http://127.0.0.1:5173", - "http://localhost:8888", # Production - "http://127.0.0.1:8888", - ], - allow_credentials=True, - allow_methods=["*"], - allow_headers=["*"], -) +# Check if remote access is enabled via environment variable +# Set by start_ui.py when --host is not 127.0.0.1 +ALLOW_REMOTE = os.environ.get("AUTOCODER_ALLOW_REMOTE", "").lower() in ("1", "true", "yes") + +# CORS - allow all origins when remote access is enabled, otherwise localhost only +if ALLOW_REMOTE: + app.add_middleware( + CORSMiddleware, + allow_origins=["*"], # Allow all origins for remote access + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) +else: + app.add_middleware( + CORSMiddleware, + allow_origins=[ + "http://localhost:5173", # Vite dev server + "http://127.0.0.1:5173", + "http://localhost:8888", # Production + "http://127.0.0.1:8888", + ], + allow_credentials=True, + allow_methods=["*"], + allow_headers=["*"], + ) # ============================================================================ # Security Middleware # ============================================================================ -@app.middleware("http") -async def require_localhost(request: Request, call_next): - """Only allow requests from localhost.""" - client_host = request.client.host if request.client else None +if not ALLOW_REMOTE: + @app.middleware("http") + async def require_localhost(request: Request, call_next): + """Only allow requests from localhost (disabled when AUTOCODER_ALLOW_REMOTE=1).""" + client_host = request.client.host if request.client else None - # Allow localhost connections - if client_host not in ("127.0.0.1", "::1", "localhost", None): - raise HTTPException(status_code=403, detail="Localhost access only") + # Allow localhost connections + if client_host not in ("127.0.0.1", "::1", "localhost", None): + raise HTTPException(status_code=403, detail="Localhost access only") - return await call_next(request) + return await call_next(request) # ============================================================================ diff --git a/start_ui.py b/start_ui.py index 7270d27..ae06b2a 100644 --- a/start_ui.py +++ b/start_ui.py @@ -13,12 +13,16 @@ Automated launcher that handles all setup: 7. Opens browser to the UI Usage: - python start_ui.py [--dev] + python start_ui.py [--dev] [--host HOST] [--port PORT] Options: - --dev Run in development mode with Vite hot reload + --dev Run in development mode with Vite hot reload + --host HOST Host to bind to (default: 127.0.0.1) + Use 0.0.0.0 for remote access (security warning will be shown) + --port PORT Port to bind to (default: 8888) """ +import argparse import asyncio import os import shutil @@ -235,26 +239,31 @@ def build_frontend() -> bool: return run_command([npm_cmd, "run", "build"], cwd=UI_DIR) -def start_dev_server(port: int) -> tuple: +def start_dev_server(port: int, host: str = "127.0.0.1") -> tuple: """Start both Vite and FastAPI in development mode.""" venv_python = get_venv_python() print("\n Starting development servers...") - print(f" - FastAPI backend: http://127.0.0.1:{port}") + print(f" - FastAPI backend: http://{host}:{port}") print(" - Vite frontend: http://127.0.0.1:5173") + # Set environment for remote access if needed + env = os.environ.copy() + if host != "127.0.0.1": + env["AUTOCODER_ALLOW_REMOTE"] = "1" + # Start FastAPI backend = subprocess.Popen([ str(venv_python), "-m", "uvicorn", "server.main:app", - "--host", "127.0.0.1", + "--host", host, "--port", str(port), "--reload" - ], cwd=str(ROOT)) + ], cwd=str(ROOT), env=env) # Start Vite with API port env var for proxy configuration npm_cmd = "npm.cmd" if sys.platform == "win32" else "npm" - vite_env = os.environ.copy() + vite_env = env.copy() vite_env["VITE_API_PORT"] = str(port) frontend = subprocess.Popen([ npm_cmd, "run", "dev" @@ -263,15 +272,18 @@ def start_dev_server(port: int) -> tuple: return backend, frontend -def start_production_server(port: int): - """Start FastAPI server in production mode with hot reload.""" +def start_production_server(port: int, host: str = "127.0.0.1"): + """Start FastAPI server in production mode.""" venv_python = get_venv_python() - print(f"\n Starting server at http://127.0.0.1:{port} (with hot reload)") + print(f"\n Starting server at http://{host}:{port}") - # Set PYTHONASYNCIODEBUG to help with Windows subprocess issues env = os.environ.copy() + # Enable remote access in server if not localhost + if host != "127.0.0.1": + env["AUTOCODER_ALLOW_REMOTE"] = "1" + # NOTE: --reload is NOT used because on Windows it breaks asyncio subprocess # support (uvicorn's reload worker doesn't inherit the ProactorEventLoop policy). # This affects Claude SDK which uses asyncio.create_subprocess_exec. @@ -279,14 +291,34 @@ def start_production_server(port: int): return subprocess.Popen([ str(venv_python), "-m", "uvicorn", "server.main:app", - "--host", "127.0.0.1", + "--host", host, "--port", str(port), ], cwd=str(ROOT), env=env) def main() -> None: """Main entry point.""" - dev_mode = "--dev" in sys.argv + parser = argparse.ArgumentParser(description="AutoCoder UI Launcher") + parser.add_argument("--dev", action="store_true", help="Run in development mode with Vite hot reload") + parser.add_argument("--host", default="127.0.0.1", help="Host to bind to (default: 127.0.0.1)") + parser.add_argument("--port", type=int, default=None, help="Port to bind to (default: auto-detect from 8888)") + args = parser.parse_args() + + dev_mode = args.dev + host = args.host + + # Security warning for remote access + if host != "127.0.0.1": + print("\n" + "!" * 50) + print(" SECURITY WARNING") + print("!" * 50) + print(f" Remote access enabled on host: {host}") + print(" The AutoCoder UI will be accessible from other machines.") + print(" Ensure you understand the security implications:") + print(" - The agent has file system access to project directories") + print(" - The API can start/stop agents and modify files") + print(" - Consider using a firewall or VPN for protection") + print("!" * 50 + "\n") print("=" * 50) print(" AutoCoder UI Setup") @@ -335,18 +367,20 @@ def main() -> None: step = 5 if dev_mode else 6 print_step(step, total_steps, "Starting server") - port = find_available_port() + port = args.port if args.port else find_available_port() try: if dev_mode: - backend, frontend = start_dev_server(port) + backend, frontend = start_dev_server(port, host) - # Open browser to Vite dev server + # Open browser to Vite dev server (always localhost for Vite) time.sleep(3) webbrowser.open("http://127.0.0.1:5173") print("\n" + "=" * 50) print(" Development mode active") + if host != "127.0.0.1": + print(f" Backend accessible at: http://{host}:{port}") print(" Press Ctrl+C to stop") print("=" * 50) @@ -362,14 +396,15 @@ def main() -> None: backend.wait() frontend.wait() else: - server = start_production_server(port) + server = start_production_server(port, host) - # Open browser + # Open browser (only if localhost) time.sleep(2) - webbrowser.open(f"http://127.0.0.1:{port}") + if host == "127.0.0.1": + webbrowser.open(f"http://127.0.0.1:{port}") print("\n" + "=" * 50) - print(f" Server running at http://127.0.0.1:{port}") + print(f" Server running at http://{host}:{port}") print(" Press Ctrl+C to stop") print("=" * 50) From d6ba075ac4b53195a0d68f5a554fa3b7526354e8 Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 12:36:54 +0100 Subject: [PATCH 15/63] style: align priority calculation pattern with rest of file Address CodeRabbit feedback - use consistent conditional pattern: `(max_priority.priority + 1) if max_priority else 1` This matches the pattern used in create_feature and create_features_bulk. Co-Authored-By: Claude Opus 4.5 --- server/routers/features.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/server/routers/features.py b/server/routers/features.py index e5ad919..c4c9c27 100644 --- a/server/routers/features.py +++ b/server/routers/features.py @@ -553,7 +553,7 @@ async def skip_feature(project_name: str, feature_id: int): # Set priority to max + 1 to push to end (consistent with MCP server) max_priority = session.query(Feature).order_by(Feature.priority.desc()).first() - feature.priority = (max_priority.priority if max_priority else 0) + 1 + feature.priority = (max_priority.priority + 1) if max_priority else 1 session.commit() From fed2516f08058b85f8b0edad0574b0c6b4c11932 Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 16:34:56 +0100 Subject: [PATCH 16/63] security: validate pkill process names against safe character set Address CodeRabbit security feedback - restrict pkill_processes entries to alphanumeric names with dots, underscores, and hyphens only. This prevents potential exploitation through regex metacharacters like '.*' being registered as process names. Changes: - Added VALID_PROCESS_NAME_PATTERN regex constant - Updated both org and project config validation to: - Normalize (trim whitespace) process names - Reject names with regex metacharacters - Reject names with spaces - Added 3 new tests for regex validation Co-Authored-By: Claude Opus 4.5 --- security.py | 25 ++++++++++++++++-- test_security.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/security.py b/security.py index 195aa92..0a22785 100644 --- a/security.py +++ b/security.py @@ -7,12 +7,17 @@ Uses an allowlist approach - only explicitly permitted commands can run. """ import os +import re import shlex from pathlib import Path from typing import Optional import yaml +# Regex pattern for valid pkill process names (no regex metacharacters allowed) +# Matches alphanumeric names with dots, underscores, and hyphens +VALID_PROCESS_NAME_PATTERN = re.compile(r"^[A-Za-z0-9._-]+$") + # Allowed commands for development tasks # Minimal set needed for the autonomous coding demo ALLOWED_COMMANDS = { @@ -474,9 +479,17 @@ def load_org_config() -> Optional[dict]: processes = config["pkill_processes"] if not isinstance(processes, list): return None + # Normalize and validate each process name against safe pattern + normalized = [] for proc in processes: - if not isinstance(proc, str) or proc.strip() == "": + if not isinstance(proc, str): return None + proc = proc.strip() + # Block empty strings and regex metacharacters + if not proc or not VALID_PROCESS_NAME_PATTERN.fullmatch(proc): + return None + normalized.append(proc) + config["pkill_processes"] = normalized return config @@ -536,9 +549,17 @@ def load_project_commands(project_dir: Path) -> Optional[dict]: processes = config["pkill_processes"] if not isinstance(processes, list): return None + # Normalize and validate each process name against safe pattern + normalized = [] for proc in processes: - if not isinstance(proc, str) or proc.strip() == "": + if not isinstance(proc, str): return None + proc = proc.strip() + # Block empty strings and regex metacharacters + if not proc or not VALID_PROCESS_NAME_PATTERN.fullmatch(proc): + return None + normalized.append(proc) + config["pkill_processes"] = normalized return config diff --git a/test_security.py b/test_security.py index 5bd1867..4bb6d24 100644 --- a/test_security.py +++ b/test_security.py @@ -809,6 +809,73 @@ pkill_processes: print(f" FAIL: pkill python should be allowed with org config: {result}") failed += 1 + # Test 9: Regex metacharacters should be rejected in pkill_processes + with tempfile.TemporaryDirectory() as tmphome: + with tempfile.TemporaryDirectory() as tmpproject: + with temporary_home(tmphome): + org_dir = Path(tmphome) / ".autocoder" + org_dir.mkdir() + org_config_path = org_dir / "config.yaml" + + # Try to register a regex pattern (should be rejected) + org_config_path.write_text("""version: 1 +pkill_processes: + - ".*" +""") + + config = load_org_config() + if config is None: + print(" PASS: Regex pattern '.*' rejected in pkill_processes") + passed += 1 + else: + print(" FAIL: Regex pattern '.*' should be rejected") + failed += 1 + + # Test 10: Valid process names with dots/underscores/hyphens should be accepted + with tempfile.TemporaryDirectory() as tmphome: + with tempfile.TemporaryDirectory() as tmpproject: + with temporary_home(tmphome): + org_dir = Path(tmphome) / ".autocoder" + org_dir.mkdir() + org_config_path = org_dir / "config.yaml" + + # Valid names with special chars + org_config_path.write_text("""version: 1 +pkill_processes: + - my-app + - app_server + - node.js +""") + + config = load_org_config() + if config is not None and config.get("pkill_processes") == ["my-app", "app_server", "node.js"]: + print(" PASS: Valid process names with dots/underscores/hyphens accepted") + passed += 1 + else: + print(f" FAIL: Valid process names should be accepted: {config}") + failed += 1 + + # Test 11: Names with spaces should be rejected + with tempfile.TemporaryDirectory() as tmphome: + with tempfile.TemporaryDirectory() as tmpproject: + with temporary_home(tmphome): + org_dir = Path(tmphome) / ".autocoder" + org_dir.mkdir() + org_config_path = org_dir / "config.yaml" + + org_config_path.write_text("""version: 1 +pkill_processes: + - "my app" +""") + + config = load_org_config() + if config is None: + print(" PASS: Process name with space rejected") + passed += 1 + else: + print(" FAIL: Process name with space should be rejected") + failed += 1 + return passed, failed From 95b0dfac83e7440d2ac35da6dd32902047ebecb9 Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 20:11:06 +0100 Subject: [PATCH 17/63] fix: Health check now fails script on server startup failure Changed from warning-only to proper error handling: - if server doesn't respond after restart, exit with error - prevents false negatives when server fails to start Co-Authored-By: Claude Opus 4.5 --- .claude/templates/coding_prompt.template.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.claude/templates/coding_prompt.template.md b/.claude/templates/coding_prompt.template.md index 4c83ea1..e7f1c2f 100644 --- a/.claude/templates/coding_prompt.template.md +++ b/.claude/templates/coding_prompt.template.md @@ -234,7 +234,10 @@ grep -r "new Map\(\)\|new Set\(\)" --include="*.ts" --include="*.tsx" --include= ./init.sh & sleep 15 # Allow server to fully start # Verify server is responding - curl -f http://localhost:3000/api/health || curl -f http://localhost:3000 || echo "WARNING: Health check failed" + if ! curl -f http://localhost:3000/api/health && ! curl -f http://localhost:3000; then + echo "ERROR: Server failed to start after restart" + exit 1 + fi ``` 5. **Query for test data - it MUST still exist** From 34b9b5f5b2f37e993c2a89efdb38c131f3054b7c Mon Sep 17 00:00:00 2001 From: cabana8471 Date: Sun, 25 Jan 2026 20:12:54 +0100 Subject: [PATCH 18/63] security: validate all pkill patterns for BSD compatibility pkill on BSD systems accepts multiple pattern operands. Previous code only validated args[-1], allowing disallowed processes to slip through when combined with allowed ones (e.g., "pkill node sshd" would only check "sshd"). Now validates every non-flag argument to ensure no disallowed process can be targeted. Added tests for multiple pattern scenarios. Addresses CodeRabbit feedback on PR #101. Co-Authored-By: Claude Opus 4.5 --- security.py | 18 ++++++++++-------- test_security.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 8 deletions(-) diff --git a/security.py b/security.py index 0a22785..44507a4 100644 --- a/security.py +++ b/security.py @@ -273,15 +273,17 @@ def validate_pkill_command( if not args: return False, "pkill requires a process name" - # The target is typically the last non-flag argument - target = args[-1] + # Validate every non-flag argument (pkill accepts multiple patterns on BSD) + # This defensively ensures no disallowed process can be targeted + targets = [] + for arg in args: + # For -f flag (full command line match), take the first word as process name + # e.g., "pkill -f 'node server.js'" -> target is "node server.js", process is "node" + t = arg.split()[0] if " " in arg else arg + targets.append(t) - # For -f flag (full command line match), extract the first word as process name - # e.g., "pkill -f 'node server.js'" -> target is "node server.js", process is "node" - if " " in target: - target = target.split()[0] - - if target in allowed_process_names: + disallowed = [t for t in targets if t not in allowed_process_names] + if not disallowed: return True, "" return False, f"pkill only allowed for processes: {sorted(allowed_process_names)}" diff --git a/test_security.py b/test_security.py index 4bb6d24..5068e1e 100644 --- a/test_security.py +++ b/test_security.py @@ -876,6 +876,34 @@ pkill_processes: print(" FAIL: Process name with space should be rejected") failed += 1 + # Test 12: Multiple patterns - all must be allowed (BSD behavior) + # On BSD, "pkill node sshd" would kill both, so we must validate all patterns + allowed, reason = validate_pkill_command("pkill node npm") + if allowed: + print(" PASS: Multiple allowed patterns accepted") + passed += 1 + else: + print(f" FAIL: Multiple allowed patterns should be accepted: {reason}") + failed += 1 + + # Test 13: Multiple patterns - block if any is disallowed + allowed, reason = validate_pkill_command("pkill node sshd") + if not allowed: + print(" PASS: Multiple patterns blocked when one is disallowed") + passed += 1 + else: + print(" FAIL: Should block when any pattern is disallowed") + failed += 1 + + # Test 14: Multiple patterns - only first allowed, second disallowed + allowed, reason = validate_pkill_command("pkill npm python") + if not allowed: + print(" PASS: Multiple patterns blocked (first allowed, second not)") + passed += 1 + else: + print(" FAIL: Should block when second pattern is disallowed") + failed += 1 + return passed, failed From 095d248a6603007e81335e72f238a69f319478e7 Mon Sep 17 00:00:00 2001 From: Auto Date: Mon, 26 Jan 2026 09:42:01 +0200 Subject: [PATCH 19/63] add ollama support --- .claude/settings.local.json | 8 ++++++++ .env.example | 17 +++++++++++++++++ CLAUDE.md | 33 +++++++++++++++++++++++++++++++++ assets/ollama.png | Bin 0 -> 3706 bytes client.py | 12 ++++++++++-- server/routers/settings.py | 12 +++++++++++- server/schemas.py | 1 + ui/public/ollama.png | Bin 0 -> 3706 bytes ui/src/App.tsx | 11 +++++++++++ ui/src/hooks/useProjects.ts | 1 + ui/src/lib/types.ts | 1 + 11 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 .claude/settings.local.json create mode 100644 assets/ollama.png create mode 100644 ui/public/ollama.png diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..cf757bf --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,8 @@ +{ + "permissions": { + "allow": [ + "Bash(copy \"C:\\\\Projects\\\\autocoder\\\\assets\\\\ollama.png\" \"C:\\\\Projects\\\\autocoder\\\\ui\\\\public\\\\ollama.png\")", + "Bash(npm run build:*)" + ] + } +} diff --git a/.env.example b/.env.example index e29bec3..aa3d9fa 100644 --- a/.env.example +++ b/.env.example @@ -19,3 +19,20 @@ # ANTHROPIC_DEFAULT_SONNET_MODEL=glm-4.7 # ANTHROPIC_DEFAULT_OPUS_MODEL=glm-4.7 # ANTHROPIC_DEFAULT_HAIKU_MODEL=glm-4.5-air + +# Ollama Local Model Configuration (Optional) +# To use local models via Ollama instead of Claude, uncomment and set these variables. +# Requires Ollama v0.14.0+ with Anthropic API compatibility. +# See: https://ollama.com/blog/claude +# +# ANTHROPIC_BASE_URL=http://localhost:11434 +# ANTHROPIC_AUTH_TOKEN=ollama +# API_TIMEOUT_MS=3000000 +# ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder +# ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3-coder +# ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder +# +# Model recommendations: +# - For best results, use a capable coding model like qwen3-coder or deepseek-coder-v2 +# - You can use the same model for all tiers, or different models per tier +# - Larger models (70B+) work best for Opus tier, smaller (7B-20B) for Haiku diff --git a/CLAUDE.md b/CLAUDE.md index 29cc2a5..c7a1b93 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -256,6 +256,39 @@ python test_security_integration.py - `examples/README.md` - Comprehensive guide with use cases, testing, and troubleshooting - `PHASE3_SPEC.md` - Specification for mid-session approval feature (future enhancement) +### Ollama Local Models (Optional) + +Run coding agents using local models via Ollama v0.14.0+: + +1. Install Ollama: https://ollama.com +2. Start Ollama: `ollama serve` +3. Pull a coding model: `ollama pull qwen3-coder` +4. Configure `.env`: + ``` + ANTHROPIC_BASE_URL=http://localhost:11434 + ANTHROPIC_AUTH_TOKEN=ollama + API_TIMEOUT_MS=3000000 + ANTHROPIC_DEFAULT_SONNET_MODEL=qwen3-coder + ANTHROPIC_DEFAULT_OPUS_MODEL=qwen3-coder + ANTHROPIC_DEFAULT_HAIKU_MODEL=qwen3-coder + ``` +5. Run autocoder normally - it will use your local Ollama models + +**Recommended coding models:** +- `qwen3-coder` - Good balance of speed and capability +- `deepseek-coder-v2` - Strong coding performance +- `codellama` - Meta's code-focused model + +**Model tier mapping:** +- Use the same model for all tiers, or map different models per capability level +- Larger models (70B+) work best for Opus tier +- Smaller models (7B-20B) work well for Haiku tier + +**Known limitations:** +- Smaller context windows than Claude (model-dependent) +- Extended context beta disabled (not supported by Ollama) +- Performance depends on local hardware (GPU recommended) + ## Claude Code Integration - `.claude/commands/create-spec.md` - `/create-spec` slash command for interactive spec creation diff --git a/assets/ollama.png b/assets/ollama.png new file mode 100644 index 0000000000000000000000000000000000000000..9f559ae7db3cc2be98a19647e5e838420da278ef GIT binary patch literal 3706 zcmV-=4u$cFP)gwv;+}y^-#(jN#k&%(s*4EF@ z&nzq~y1Kfvv$KbXhe}FHZEbCIbaa=Oml+uuQBhH2V`Gerj4?4WMMXt9IXQWGc_$|) z4Gj%jTU$m(Mj|32JUl#SXJ;H791{~0`3Kdc000fKNklU7sE1QM<%JyQVsXTy|IHyQbMeD6Q0l#Yr7womL877#m>x9U*sC^ zgPx^>B}|5S>HnfF*#Rfm6mIapqfoIs>%d8Nz=bBhddRJGpvR0;xa$v5>O!^?R8XTC3=?qe)dSdh6l1rFD| zxY#xQ`z-8*o!Aj+zZ>w<+zSOSptu_eP?&+kypb7DBfG*6fWmtg54jHq~aQQGLNPA5?kQL7GAjnXhk1 z92EqoTdBgT!!ZS4pY`yek{(i(RU%^`(!*|I#qMjpS&p9fYfE}U(?hCf>!8~LSLt>< zBwub$7JK7YuepaNUAi1LRY8rR!D4bYhDQH&tWsc%m@d`Y!+#57F;RD{`-3g}R^6J$ z<|fV)1uu3lq@nR#Ip0*bwE&-Hf{)ZGakr?asvY(Xx?^i$Olez~U;2+Vb?e2ADcg~) zeHxS1(nFfY#7ORuwbWNFblBgqyiQ6U)sjQ}(7o_}Fr7OZY5~h`zjYR^=|mg;*HW)Q zgZAG=b3f@Uw}}Q5x2O#I1paQbI+&Rs^3l{St30UkUar1)vo0tWtG=4V{JIMCf3;+v zyqJl?8)oemc|a{Zjd{2U3g@0|B}fFd)VFbkqJ_pwb3o%QXi!0aRJ7df2r^q{S3E~O z=`|m!B1EoF`21J8Gg@%Fu0dFg<3Y~NYS@Q;d-QrjSUbEpkjg1xpG_F-c(mQ3D`!ks z?iM6J;eLmgny%FOwr#-}c4-#>kt-&A3YXzJi*HLbwc=uX?; z`f|+50u1dE{a6+8unS|b(si<;{EW`&SXSD-6Iz-KWF-!`hOr;jbP8hn#^3x;`lQ?e z%GTf4RK9b7Vn;%XA02q-@2K~5K#w0m5;u#S3H_@3@MDAT#^TY79X}{O#(_Cj(IH$s zG>+dvCA<>Vr|k5>xBD+D0dhj3$_9yxI!^@vSb9=0%wQZ!jI)3zpuQ8V7*-FE!~)tZ zIcNAPHsKKsXo^_66wk6mI(2*olH*cRmz9iLAiyw7Z5SA~*cUkf4IpIoVUfl={eK4T zL>ZY7_|^~)Ixv!N4t05VA`(jh2x*bR1y~lb^~?d3*5E~cL4d=CwInDV06t7YAXp&j zpezL7x=92W)a#Dsb5nqqEI};Nd!U6r1si50!zY ziA0Im0(6P0OjB&nQMFMe^K9QJ4H^m^=bBYSXqXsUQ6Lc?g2`6{L{#p_X`n0Do$aU7 zcpM+h^{N{fmW@a{pK@E8aExmA*v(`JRolA$il0m#|1> z?)-#cMITcS+T->`tLLi2=oBl;CwK79eQLB;3;WiH+ui+I-*Fl)gkYx@t zI$mq@vOhHUkP)xKNfcx&%stG$r}MezmL7VnQtY5~rVk2CEml7ZrgS50>)~Y|ST4{{ zp@-77>>ymFYcgcfq>hlc_3%P?S!xfkj8{09iHp=0Gh_-=GsZ3R`pWdAMM1JUUfD^6 z>Rc;xkVS`@F+Te>y0|>klQvQS$60g|(Gpn)FK0D0_IP1hr1yTpkxMPq1qXotav0j| zdt1%J-s4C+Nf|d|FWyGaL-K$Z{BVead&ylZ|5EdO1Rcy3Fe-wzFR)0R3}yaR-!A5Z z^3U;2q-W(y*}>y=@`FSl%)GLDgspRugD{B}t5p!N!QEe{k;to4r%%Zi*g9pS&hu!y zAE;0db_?xR$J)m!9E9;;KOvBla!rBO_Bpb~f~}>gqOrHaM3awfXiV^rp)i20@Zvyu z-gLKVPtjX%Oghgzh3J=qpcMRP@y=*L7lvn*mG)+dx6SkNeKZ<9@3jej&m;>(i880V z8Uf9?>swBn>)~s^Ven+m=7SCL-`jo{^<41cZ`;i~C6v;#iP$om_d)43tEU6as}Rt`sQ@Y7n;q@}w&P%cII3 z?yjPT|Nm9`|JO7$BA2)vq(P)6qH@ ze>W=_DG^x5#r-aXat>yciGZts_*w1}EJiihiI2GW>F{y@>m`#1hFY4se$CYkVBm$~ zReC(6g4_H`v_{H{bd|E#3J7(iZgc{IGWL;tsCAXPJQ4DN5=W^@0M56_g*t^w*{cN@ zL%D0mpfp!1XBPylk(Be2F>PJXZ}RE^O0Vb-@_Gx7s7|riL{jNtV!oaq<<$XiX%!WX zSBFX!2owcgc`qC1u%)hhyUuA`R0#92=Upt1DHco~Hv#sK!7-p=auo=*T z3cN?C?}@@lK@a6wKQu*AcvG;-@{AD#o^hf?s3nuX!4ZChA<_+)DQ$!Fy<7Mk# zwG+}xq=vzMy?7`_SJXqgp5(|j>g~p?7dn8RMwO$cA1aX=2BK*sQ;7LJVzCz~%|(k+ z)0cak#4J!aGFnWyEsKK=+r{35N1U~SJYm!uUd5rOUo__L@11LD%ndi(aKjBZ+;A26 Y4^M=S;B7=Er~m)}07*qoM6N<$f+*q<_5c6? literal 0 HcmV?d00001 diff --git a/client.py b/client.py index e844aa4..7994f64 100644 --- a/client.py +++ b/client.py @@ -257,9 +257,16 @@ def create_client( if value: sdk_env[var] = value + # Detect alternative API mode (Ollama or GLM) + base_url = sdk_env.get("ANTHROPIC_BASE_URL", "") + is_alternative_api = bool(base_url) + is_ollama = "localhost:11434" in base_url or "127.0.0.1:11434" in base_url + if sdk_env: print(f" - API overrides: {', '.join(sdk_env.keys())}") - if "ANTHROPIC_BASE_URL" in sdk_env: + if is_ollama: + print(" - Ollama Mode: Using local models") + elif "ANTHROPIC_BASE_URL" in sdk_env: print(f" - GLM Mode: Using {sdk_env['ANTHROPIC_BASE_URL']}") # Create a wrapper for bash_security_hook that passes project_dir via context @@ -336,7 +343,8 @@ def create_client( # Enable extended context beta for better handling of long sessions. # This provides up to 1M tokens of context with automatic compaction. # See: https://docs.anthropic.com/en/api/beta-headers - betas=["context-1m-2025-08-07"], + # Disabled for alternative APIs (Ollama, GLM) as they don't support Claude-specific betas. + betas=[] if is_alternative_api else ["context-1m-2025-08-07"], # Note on context management: # The Claude Agent SDK handles context management automatically through the # underlying Claude Code CLI. When context approaches limits, the CLI diff --git a/server/routers/settings.py b/server/routers/settings.py index cf16045..8f3f906 100644 --- a/server/routers/settings.py +++ b/server/routers/settings.py @@ -40,7 +40,15 @@ def _parse_yolo_mode(value: str | None) -> bool: def _is_glm_mode() -> bool: """Check if GLM API is configured via environment variables.""" - return bool(os.getenv("ANTHROPIC_BASE_URL")) + base_url = os.getenv("ANTHROPIC_BASE_URL", "") + # GLM mode is when ANTHROPIC_BASE_URL is set but NOT pointing to Ollama + return bool(base_url) and not _is_ollama_mode() + + +def _is_ollama_mode() -> bool: + """Check if Ollama API is configured via environment variables.""" + base_url = os.getenv("ANTHROPIC_BASE_URL", "") + return "localhost:11434" in base_url or "127.0.0.1:11434" in base_url @router.get("/models", response_model=ModelsResponse) @@ -82,6 +90,7 @@ async def get_settings(): yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")), model=all_settings.get("model", DEFAULT_MODEL), glm_mode=_is_glm_mode(), + ollama_mode=_is_ollama_mode(), testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1), ) @@ -104,5 +113,6 @@ async def update_settings(update: SettingsUpdate): yolo_mode=_parse_yolo_mode(all_settings.get("yolo_mode")), model=all_settings.get("model", DEFAULT_MODEL), glm_mode=_is_glm_mode(), + ollama_mode=_is_ollama_mode(), testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1), ) diff --git a/server/schemas.py b/server/schemas.py index 844aaa1..0a2807c 100644 --- a/server/schemas.py +++ b/server/schemas.py @@ -382,6 +382,7 @@ class SettingsResponse(BaseModel): yolo_mode: bool = False model: str = DEFAULT_MODEL glm_mode: bool = False # True if GLM API is configured via .env + ollama_mode: bool = False # True if Ollama API is configured via .env testing_agent_ratio: int = 1 # Regression testing agents (0-3) diff --git a/ui/public/ollama.png b/ui/public/ollama.png new file mode 100644 index 0000000000000000000000000000000000000000..9f559ae7db3cc2be98a19647e5e838420da278ef GIT binary patch literal 3706 zcmV-=4u$cFP)gwv;+}y^-#(jN#k&%(s*4EF@ z&nzq~y1Kfvv$KbXhe}FHZEbCIbaa=Oml+uuQBhH2V`Gerj4?4WMMXt9IXQWGc_$|) z4Gj%jTU$m(Mj|32JUl#SXJ;H791{~0`3Kdc000fKNklU7sE1QM<%JyQVsXTy|IHyQbMeD6Q0l#Yr7womL877#m>x9U*sC^ zgPx^>B}|5S>HnfF*#Rfm6mIapqfoIs>%d8Nz=bBhddRJGpvR0;xa$v5>O!^?R8XTC3=?qe)dSdh6l1rFD| zxY#xQ`z-8*o!Aj+zZ>w<+zSOSptu_eP?&+kypb7DBfG*6fWmtg54jHq~aQQGLNPA5?kQL7GAjnXhk1 z92EqoTdBgT!!ZS4pY`yek{(i(RU%^`(!*|I#qMjpS&p9fYfE}U(?hCf>!8~LSLt>< zBwub$7JK7YuepaNUAi1LRY8rR!D4bYhDQH&tWsc%m@d`Y!+#57F;RD{`-3g}R^6J$ z<|fV)1uu3lq@nR#Ip0*bwE&-Hf{)ZGakr?asvY(Xx?^i$Olez~U;2+Vb?e2ADcg~) zeHxS1(nFfY#7ORuwbWNFblBgqyiQ6U)sjQ}(7o_}Fr7OZY5~h`zjYR^=|mg;*HW)Q zgZAG=b3f@Uw}}Q5x2O#I1paQbI+&Rs^3l{St30UkUar1)vo0tWtG=4V{JIMCf3;+v zyqJl?8)oemc|a{Zjd{2U3g@0|B}fFd)VFbkqJ_pwb3o%QXi!0aRJ7df2r^q{S3E~O z=`|m!B1EoF`21J8Gg@%Fu0dFg<3Y~NYS@Q;d-QrjSUbEpkjg1xpG_F-c(mQ3D`!ks z?iM6J;eLmgny%FOwr#-}c4-#>kt-&A3YXzJi*HLbwc=uX?; z`f|+50u1dE{a6+8unS|b(si<;{EW`&SXSD-6Iz-KWF-!`hOr;jbP8hn#^3x;`lQ?e z%GTf4RK9b7Vn;%XA02q-@2K~5K#w0m5;u#S3H_@3@MDAT#^TY79X}{O#(_Cj(IH$s zG>+dvCA<>Vr|k5>xBD+D0dhj3$_9yxI!^@vSb9=0%wQZ!jI)3zpuQ8V7*-FE!~)tZ zIcNAPHsKKsXo^_66wk6mI(2*olH*cRmz9iLAiyw7Z5SA~*cUkf4IpIoVUfl={eK4T zL>ZY7_|^~)Ixv!N4t05VA`(jh2x*bR1y~lb^~?d3*5E~cL4d=CwInDV06t7YAXp&j zpezL7x=92W)a#Dsb5nqqEI};Nd!U6r1si50!zY ziA0Im0(6P0OjB&nQMFMe^K9QJ4H^m^=bBYSXqXsUQ6Lc?g2`6{L{#p_X`n0Do$aU7 zcpM+h^{N{fmW@a{pK@E8aExmA*v(`JRolA$il0m#|1> z?)-#cMITcS+T->`tLLi2=oBl;CwK79eQLB;3;WiH+ui+I-*Fl)gkYx@t zI$mq@vOhHUkP)xKNfcx&%stG$r}MezmL7VnQtY5~rVk2CEml7ZrgS50>)~Y|ST4{{ zp@-77>>ymFYcgcfq>hlc_3%P?S!xfkj8{09iHp=0Gh_-=GsZ3R`pWdAMM1JUUfD^6 z>Rc;xkVS`@F+Te>y0|>klQvQS$60g|(Gpn)FK0D0_IP1hr1yTpkxMPq1qXotav0j| zdt1%J-s4C+Nf|d|FWyGaL-K$Z{BVead&ylZ|5EdO1Rcy3Fe-wzFR)0R3}yaR-!A5Z z^3U;2q-W(y*}>y=@`FSl%)GLDgspRugD{B}t5p!N!QEe{k;to4r%%Zi*g9pS&hu!y zAE;0db_?xR$J)m!9E9;;KOvBla!rBO_Bpb~f~}>gqOrHaM3awfXiV^rp)i20@Zvyu z-gLKVPtjX%Oghgzh3J=qpcMRP@y=*L7lvn*mG)+dx6SkNeKZ<9@3jej&m;>(i880V z8Uf9?>swBn>)~s^Ven+m=7SCL-`jo{^<41cZ`;i~C6v;#iP$om_d)43tEU6as}Rt`sQ@Y7n;q@}w&P%cII3 z?yjPT|Nm9`|JO7$BA2)vq(P)6qH@ ze>W=_DG^x5#r-aXat>yciGZts_*w1}EJiihiI2GW>F{y@>m`#1hFY4se$CYkVBm$~ zReC(6g4_H`v_{H{bd|E#3J7(iZgc{IGWL;tsCAXPJQ4DN5=W^@0M56_g*t^w*{cN@ zL%D0mpfp!1XBPylk(Be2F>PJXZ}RE^O0Vb-@_Gx7s7|riL{jNtV!oaq<<$XiX%!WX zSBFX!2owcgc`qC1u%)hhyUuA`R0#92=Upt1DHco~Hv#sK!7-p=auo=*T z3cN?C?}@@lK@a6wKQu*AcvG;-@{AD#o^hf?s3nuX!4ZChA<_+)DQ$!Fy<7Mk# zwG+}xq=vzMy?7`_SJXqgp5(|j>g~p?7dn8RMwO$cA1aX=2BK*sQ;7LJVzCz~%|(k+ z)0cak#4J!aGFnWyEsKK=+r{35N1U~SJYm!uUd5rOUo__L@11LD%ndi(aKjBZ+;A26 Y4^M=S;B7=Er~m)}07*qoM6N<$f+*q<_5c6? literal 0 HcmV?d00001 diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 59ed0ab..0483ab7 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -298,6 +298,17 @@ function App() { + {/* Ollama Mode Indicator */} + {settings?.ollama_mode && ( +
+ Ollama + Ollama +
+ )} + {/* GLM Mode Badge */} {settings?.glm_mode && ( Date: Mon, 26 Jan 2026 09:49:21 +0200 Subject: [PATCH 20/63] chore: remove duplicate asset and gitignore local settings - Remove assets/ollama.png (duplicate of ui/public/ollama.png) - Remove .claude/settings.local.json from tracking - Add .claude/settings.local.json to .gitignore Co-Authored-By: Claude Opus 4.5 --- .claude/settings.local.json | 8 -------- .gitignore | 5 +++++ assets/ollama.png | Bin 3706 -> 0 bytes 3 files changed, 5 insertions(+), 8 deletions(-) delete mode 100644 .claude/settings.local.json delete mode 100644 assets/ollama.png diff --git a/.claude/settings.local.json b/.claude/settings.local.json deleted file mode 100644 index cf757bf..0000000 --- a/.claude/settings.local.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "permissions": { - "allow": [ - "Bash(copy \"C:\\\\Projects\\\\autocoder\\\\assets\\\\ollama.png\" \"C:\\\\Projects\\\\autocoder\\\\ui\\\\public\\\\ollama.png\")", - "Bash(npm run build:*)" - ] - } -} diff --git a/.gitignore b/.gitignore index d90c6ca..bb20118 100644 --- a/.gitignore +++ b/.gitignore @@ -76,6 +76,11 @@ ui/playwright-report/ .dmypy.json dmypy.json +# =================== +# Claude Code +# =================== +.claude/settings.local.json + # =================== # IDE / Editors # =================== diff --git a/assets/ollama.png b/assets/ollama.png deleted file mode 100644 index 9f559ae7db3cc2be98a19647e5e838420da278ef..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3706 zcmV-=4u$cFP)gwv;+}y^-#(jN#k&%(s*4EF@ z&nzq~y1Kfvv$KbXhe}FHZEbCIbaa=Oml+uuQBhH2V`Gerj4?4WMMXt9IXQWGc_$|) z4Gj%jTU$m(Mj|32JUl#SXJ;H791{~0`3Kdc000fKNklU7sE1QM<%JyQVsXTy|IHyQbMeD6Q0l#Yr7womL877#m>x9U*sC^ zgPx^>B}|5S>HnfF*#Rfm6mIapqfoIs>%d8Nz=bBhddRJGpvR0;xa$v5>O!^?R8XTC3=?qe)dSdh6l1rFD| zxY#xQ`z-8*o!Aj+zZ>w<+zSOSptu_eP?&+kypb7DBfG*6fWmtg54jHq~aQQGLNPA5?kQL7GAjnXhk1 z92EqoTdBgT!!ZS4pY`yek{(i(RU%^`(!*|I#qMjpS&p9fYfE}U(?hCf>!8~LSLt>< zBwub$7JK7YuepaNUAi1LRY8rR!D4bYhDQH&tWsc%m@d`Y!+#57F;RD{`-3g}R^6J$ z<|fV)1uu3lq@nR#Ip0*bwE&-Hf{)ZGakr?asvY(Xx?^i$Olez~U;2+Vb?e2ADcg~) zeHxS1(nFfY#7ORuwbWNFblBgqyiQ6U)sjQ}(7o_}Fr7OZY5~h`zjYR^=|mg;*HW)Q zgZAG=b3f@Uw}}Q5x2O#I1paQbI+&Rs^3l{St30UkUar1)vo0tWtG=4V{JIMCf3;+v zyqJl?8)oemc|a{Zjd{2U3g@0|B}fFd)VFbkqJ_pwb3o%QXi!0aRJ7df2r^q{S3E~O z=`|m!B1EoF`21J8Gg@%Fu0dFg<3Y~NYS@Q;d-QrjSUbEpkjg1xpG_F-c(mQ3D`!ks z?iM6J;eLmgny%FOwr#-}c4-#>kt-&A3YXzJi*HLbwc=uX?; z`f|+50u1dE{a6+8unS|b(si<;{EW`&SXSD-6Iz-KWF-!`hOr;jbP8hn#^3x;`lQ?e z%GTf4RK9b7Vn;%XA02q-@2K~5K#w0m5;u#S3H_@3@MDAT#^TY79X}{O#(_Cj(IH$s zG>+dvCA<>Vr|k5>xBD+D0dhj3$_9yxI!^@vSb9=0%wQZ!jI)3zpuQ8V7*-FE!~)tZ zIcNAPHsKKsXo^_66wk6mI(2*olH*cRmz9iLAiyw7Z5SA~*cUkf4IpIoVUfl={eK4T zL>ZY7_|^~)Ixv!N4t05VA`(jh2x*bR1y~lb^~?d3*5E~cL4d=CwInDV06t7YAXp&j zpezL7x=92W)a#Dsb5nqqEI};Nd!U6r1si50!zY ziA0Im0(6P0OjB&nQMFMe^K9QJ4H^m^=bBYSXqXsUQ6Lc?g2`6{L{#p_X`n0Do$aU7 zcpM+h^{N{fmW@a{pK@E8aExmA*v(`JRolA$il0m#|1> z?)-#cMITcS+T->`tLLi2=oBl;CwK79eQLB;3;WiH+ui+I-*Fl)gkYx@t zI$mq@vOhHUkP)xKNfcx&%stG$r}MezmL7VnQtY5~rVk2CEml7ZrgS50>)~Y|ST4{{ zp@-77>>ymFYcgcfq>hlc_3%P?S!xfkj8{09iHp=0Gh_-=GsZ3R`pWdAMM1JUUfD^6 z>Rc;xkVS`@F+Te>y0|>klQvQS$60g|(Gpn)FK0D0_IP1hr1yTpkxMPq1qXotav0j| zdt1%J-s4C+Nf|d|FWyGaL-K$Z{BVead&ylZ|5EdO1Rcy3Fe-wzFR)0R3}yaR-!A5Z z^3U;2q-W(y*}>y=@`FSl%)GLDgspRugD{B}t5p!N!QEe{k;to4r%%Zi*g9pS&hu!y zAE;0db_?xR$J)m!9E9;;KOvBla!rBO_Bpb~f~}>gqOrHaM3awfXiV^rp)i20@Zvyu z-gLKVPtjX%Oghgzh3J=qpcMRP@y=*L7lvn*mG)+dx6SkNeKZ<9@3jej&m;>(i880V z8Uf9?>swBn>)~s^Ven+m=7SCL-`jo{^<41cZ`;i~C6v;#iP$om_d)43tEU6as}Rt`sQ@Y7n;q@}w&P%cII3 z?yjPT|Nm9`|JO7$BA2)vq(P)6qH@ ze>W=_DG^x5#r-aXat>yciGZts_*w1}EJiihiI2GW>F{y@>m`#1hFY4se$CYkVBm$~ zReC(6g4_H`v_{H{bd|E#3J7(iZgc{IGWL;tsCAXPJQ4DN5=W^@0M56_g*t^w*{cN@ zL%D0mpfp!1XBPylk(Be2F>PJXZ}RE^O0Vb-@_Gx7s7|riL{jNtV!oaq<<$XiX%!WX zSBFX!2owcgc`qC1u%)hhyUuA`R0#92=Upt1DHco~Hv#sK!7-p=auo=*T z3cN?C?}@@lK@a6wKQu*AcvG;-@{AD#o^hf?s3nuX!4ZChA<_+)DQ$!Fy<7Mk# zwG+}xq=vzMy?7`_SJXqgp5(|j>g~p?7dn8RMwO$cA1aX=2BK*sQ;7LJVzCz~%|(k+ z)0cak#4J!aGFnWyEsKK=+r{35N1U~SJYm!uUd5rOUo__L@11LD%ndi(aKjBZ+;A26 Y4^M=S;B7=Er~m)}07*qoM6N<$f+*q<_5c6? From 468e59f86c311370d264e4a885ef6a50f01cf7fb Mon Sep 17 00:00:00 2001 From: nioasoft Date: Mon, 26 Jan 2026 12:26:06 +0200 Subject: [PATCH 21/63] fix: handle 404 errors for deleted assistant conversations When a stored conversation ID no longer exists (e.g., database was reset or conversation was deleted), the UI would repeatedly try to fetch it, causing endless 404 errors in the console. This fix: - Stops retrying on 404 errors (conversation doesn't exist) - Automatically clears the stored conversation ID from localStorage when a 404 is received, allowing the user to start fresh Co-Authored-By: Claude Opus 4.5 --- ui/src/components/AssistantPanel.tsx | 10 +++++++++- ui/src/hooks/useConversations.ts | 7 +++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/ui/src/components/AssistantPanel.tsx b/ui/src/components/AssistantPanel.tsx index 5efe624..2093564 100644 --- a/ui/src/components/AssistantPanel.tsx +++ b/ui/src/components/AssistantPanel.tsx @@ -49,11 +49,19 @@ export function AssistantPanel({ projectName, isOpen, onClose }: AssistantPanelP ) // Fetch conversation details when we have an ID - const { data: conversationDetail, isLoading: isLoadingConversation } = useConversation( + const { data: conversationDetail, isLoading: isLoadingConversation, error: conversationError } = useConversation( projectName, conversationId ) + // Clear stored conversation ID if it no longer exists (404 error) + useEffect(() => { + if (conversationError && conversationId) { + console.warn(`Conversation ${conversationId} not found, clearing stored ID`) + setConversationId(null) + } + }, [conversationError, conversationId]) + // Convert API messages to ChatMessage format for the chat component const initialMessages: ChatMessage[] | undefined = conversationDetail?.messages.map((msg) => ({ id: `db-${msg.id}`, diff --git a/ui/src/hooks/useConversations.ts b/ui/src/hooks/useConversations.ts index 908b22d..0a59534 100644 --- a/ui/src/hooks/useConversations.ts +++ b/ui/src/hooks/useConversations.ts @@ -26,6 +26,13 @@ export function useConversation(projectName: string | null, conversationId: numb queryFn: () => api.getAssistantConversation(projectName!, conversationId!), enabled: !!projectName && !!conversationId, staleTime: 30_000, // Cache for 30 seconds + retry: (failureCount, error) => { + // Don't retry on 404 errors (conversation doesn't exist) + if (error instanceof Error && error.message.includes('404')) { + return false + } + return failureCount < 3 + }, }) } From fe5f58cf45190102f932f8ec8d92ce4f30408a9a Mon Sep 17 00:00:00 2001 From: Auto Date: Mon, 26 Jan 2026 12:41:01 +0200 Subject: [PATCH 22/63] add a pr review command --- .claude/commands/review-pr.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .claude/commands/review-pr.md diff --git a/.claude/commands/review-pr.md b/.claude/commands/review-pr.md new file mode 100644 index 0000000..62e8bf9 --- /dev/null +++ b/.claude/commands/review-pr.md @@ -0,0 +1,15 @@ +--- +description: Review pull requests +--- + +Pull request(s): $ARGUMENTS + +- If no PR numbers are provided, ask the user to provide PR number(s). +- At least 1 PR is required. + +## TASKS +- Use the GH CLI tool to retrieve the details (descriptions, divs, comments, feedback, reviews, etc) +- Use 3 deepdive subagents to analyze the impact of the codebase +- Provide a review on whether the PR is safe to merge as-is +- Provide any feedback in terms of risk level +- Propose any improments in terms of importance and complexity \ No newline at end of file From 2b07625ce4c6082d25e848f86a49f8b1ccfd79bb Mon Sep 17 00:00:00 2001 From: nioasoft Date: Mon, 26 Jan 2026 12:47:21 +0200 Subject: [PATCH 23/63] fix: improve 404 detection for deleted conversations - Check for 'not found' message (server response) in addition to '404' - Only clear stored conversation ID on actual 404 errors - Prevent unnecessary retries for deleted conversations - Don't clear conversation on transient network errors Co-Authored-By: Claude Opus 4.5 --- ui/src/components/AssistantPanel.tsx | 8 ++++++-- ui/src/hooks/useConversations.ts | 7 +++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/ui/src/components/AssistantPanel.tsx b/ui/src/components/AssistantPanel.tsx index 2093564..c02d380 100644 --- a/ui/src/components/AssistantPanel.tsx +++ b/ui/src/components/AssistantPanel.tsx @@ -57,8 +57,12 @@ export function AssistantPanel({ projectName, isOpen, onClose }: AssistantPanelP // Clear stored conversation ID if it no longer exists (404 error) useEffect(() => { if (conversationError && conversationId) { - console.warn(`Conversation ${conversationId} not found, clearing stored ID`) - setConversationId(null) + const message = conversationError.message.toLowerCase() + // Only clear for 404 errors, not transient network issues + if (message.includes('not found') || message.includes('404')) { + console.warn(`Conversation ${conversationId} not found, clearing stored ID`) + setConversationId(null) + } } }, [conversationError, conversationId]) diff --git a/ui/src/hooks/useConversations.ts b/ui/src/hooks/useConversations.ts index 0a59534..c3b50de 100644 --- a/ui/src/hooks/useConversations.ts +++ b/ui/src/hooks/useConversations.ts @@ -27,8 +27,11 @@ export function useConversation(projectName: string | null, conversationId: numb enabled: !!projectName && !!conversationId, staleTime: 30_000, // Cache for 30 seconds retry: (failureCount, error) => { - // Don't retry on 404 errors (conversation doesn't exist) - if (error instanceof Error && error.message.includes('404')) { + // Don't retry on "not found" errors (404) - conversation doesn't exist + if (error instanceof Error && ( + error.message.toLowerCase().includes('not found') || + error.message === 'HTTP 404' + )) { return false } return failureCount < 3 From dd0a34a138269d6526f969a26fff93315c9b0f58 Mon Sep 17 00:00:00 2001 From: Auto Date: Mon, 26 Jan 2026 16:30:59 +0200 Subject: [PATCH 24/63] fix: address PR #93 review issues - Remove translate-x/translate-y CSS selectors that broke layout utilities (AssistantPanel slide animation, DebugLogViewer resize handle) - Add browser validation to get_playwright_browser() with warning for invalid values (matches get_playwright_headless() behavior) - Remove phantom SQLite documentation from CUSTOM_UPDATES.md that described features not present in PR #93 - Update checklist and revert instructions to match actual changes Co-Authored-By: Claude Opus 4.5 --- CUSTOM_UPDATES.md | 110 ++------------------------------- client.py | 12 +++- ui/src/styles/custom-theme.css | 5 +- 3 files changed, 18 insertions(+), 109 deletions(-) diff --git a/CUSTOM_UPDATES.md b/CUSTOM_UPDATES.md index 9a3bd4e..f211696 100644 --- a/CUSTOM_UPDATES.md +++ b/CUSTOM_UPDATES.md @@ -8,8 +8,7 @@ This document tracks all customizations made to AutoCoder that deviate from the 1. [UI Theme Customization](#1-ui-theme-customization) 2. [Playwright Browser Configuration](#2-playwright-browser-configuration) -3. [SQLite Robust Connection Handling](#3-sqlite-robust-connection-handling) -4. [Update Checklist](#update-checklist) +3. [Update Checklist](#update-checklist) --- @@ -175,95 +174,7 @@ playwright_args = [ --- -## 3. SQLite Robust Connection Handling - -### Overview - -Added robust SQLite connection handling to prevent database corruption from concurrent access (MCP server, FastAPI server, progress tracking). - -**Features Added:** -- WAL mode for better concurrency -- Busy timeout (30 seconds) -- Retry logic with exponential backoff -- Database health check endpoint - -### Modified Files - -#### `api/database.py` - -**New functions added:** - -```python -def get_robust_connection(db_path: str) -> sqlite3.Connection: - """ - Create a SQLite connection with robust settings: - - WAL mode for concurrent access - - 30 second busy timeout - - Foreign keys enabled - """ - -@contextmanager -def robust_db_connection(db_path: str): - """Context manager for robust database connections.""" - -def execute_with_retry(conn, sql, params=None, max_retries=3): - """Execute SQL with exponential backoff retry for transient errors.""" - -def check_database_health(db_path: str) -> dict: - """ - Check database integrity and return health status. - Returns: {healthy: bool, message: str, details: dict} - """ -``` - ---- - -#### `progress.py` - -**Changed from raw sqlite3 to robust connections:** - -```python -# BEFORE: -conn = sqlite3.connect(db_path) - -# AFTER: -from api.database import robust_db_connection, execute_with_retry - -with robust_db_connection(db_path) as conn: - execute_with_retry(conn, sql, params) -``` - ---- - -#### `server/routers/projects.py` - -**New endpoint added:** - -```python -@router.get("/{project_name}/db-health") -async def get_database_health(project_name: str) -> DatabaseHealth: - """ - Check the health of the project's features database. - Useful for diagnosing corruption issues. - """ -``` - ---- - -#### `server/schemas.py` - -**New schema added:** - -```python -class DatabaseHealth(BaseModel): - healthy: bool - message: str - details: dict = {} -``` - ---- - -## Update Checklist +## 3. Update Checklist When updating AutoCoder from upstream, verify these items: @@ -276,13 +187,8 @@ When updating AutoCoder from upstream, verify these items: ### Backend Changes - [ ] `client.py` - Playwright browser/headless defaults preserved - [ ] `.env.example` - Documentation updates preserved -- [ ] `api/database.py` - Robust connection functions preserved -- [ ] `progress.py` - Uses robust_db_connection -- [ ] `server/routers/projects.py` - db-health endpoint preserved -- [ ] `server/schemas.py` - DatabaseHealth schema preserved ### General -- [ ] Test database operations under concurrent load - [ ] Verify Playwright uses Firefox by default - [ ] Check that browser runs headless by default @@ -299,8 +205,7 @@ cd ui && npm run build ### Backend Only ```bash -git checkout client.py .env.example api/database.py progress.py -git checkout server/routers/projects.py server/schemas.py +git checkout client.py .env.example ``` --- @@ -311,18 +216,13 @@ git checkout server/routers/projects.py server/schemas.py |------|------|-------------------| | `ui/src/styles/custom-theme.css` | UI | Twitter-style theme | | `ui/src/components/KanbanColumn.tsx` | UI | Themeable kanban columns | +| `ui/src/main.tsx` | UI | Imports custom theme | | `client.py` | Backend | Firefox + headless defaults | | `.env.example` | Config | Updated documentation | -| `api/database.py` | Backend | Robust SQLite connections | -| `progress.py` | Backend | Uses robust connections | -| `server/routers/projects.py` | Backend | db-health endpoint | -| `server/schemas.py` | Backend | DatabaseHealth schema | --- ## Last Updated **Date:** January 2026 -**Commits:** -- `1910b96` - SQLite robust connection handling -- `e014b04` - Custom theme override system +**PR:** #93 - Twitter-style UI theme with custom theme override system diff --git a/client.py b/client.py index 80573a9..7ea04a5 100644 --- a/client.py +++ b/client.py @@ -59,6 +59,10 @@ def get_playwright_headless() -> bool: return value in truthy +# Valid browsers supported by Playwright MCP +VALID_PLAYWRIGHT_BROWSERS = {"chrome", "firefox", "webkit", "msedge"} + + def get_playwright_browser() -> str: """ Get the browser to use for Playwright. @@ -67,7 +71,13 @@ def get_playwright_browser() -> str: Options: chrome, firefox, webkit, msedge Firefox is recommended for lower CPU usage. """ - return os.getenv("PLAYWRIGHT_BROWSER", DEFAULT_PLAYWRIGHT_BROWSER).lower() + value = os.getenv("PLAYWRIGHT_BROWSER", DEFAULT_PLAYWRIGHT_BROWSER).strip().lower() + if value not in VALID_PLAYWRIGHT_BROWSERS: + print(f" - Warning: Invalid PLAYWRIGHT_BROWSER='{value}', " + f"valid options: {', '.join(sorted(VALID_PLAYWRIGHT_BROWSERS))}. " + f"Defaulting to {DEFAULT_PLAYWRIGHT_BROWSER}") + return DEFAULT_PLAYWRIGHT_BROWSER + return value # Feature MCP tools for feature/test management diff --git a/ui/src/styles/custom-theme.css b/ui/src/styles/custom-theme.css index 1d7a032..7a673b8 100644 --- a/ui/src/styles/custom-theme.css +++ b/ui/src/styles/custom-theme.css @@ -298,10 +298,9 @@ a:focus-visible, box-shadow: none !important; } +/* Only disable hover transforms, not layout utilities like -translate-x-1/2 */ [class*="hover:translate"], -[class*="hover:-translate"], -[class*="translate-x"], -[class*="translate-y"] { +[class*="hover:-translate"] { transform: none !important; } From e45b5b064eb46685b0696e19ecb15a8f6a6874ce Mon Sep 17 00:00:00 2001 From: Auto Date: Mon, 26 Jan 2026 16:34:48 +0200 Subject: [PATCH 25/63] chore: remove unused import in test_security.py Co-Authored-By: Claude Opus 4.5 --- test_security.py | 1 - 1 file changed, 1 deletion(-) diff --git a/test_security.py b/test_security.py index 5068e1e..1bd48d9 100644 --- a/test_security.py +++ b/test_security.py @@ -15,7 +15,6 @@ from contextlib import contextmanager from pathlib import Path from security import ( - DEFAULT_PKILL_PROCESSES, bash_security_hook, extract_commands, get_effective_commands, From c917582a6424437ac35dbb5e1c7b6b9661a5fee9 Mon Sep 17 00:00:00 2001 From: Auto Date: Mon, 26 Jan 2026 18:25:55 +0200 Subject: [PATCH 26/63] refactor(ui): migrate to shadcn/ui components and fix scroll issues Migrate UI component library from custom implementations to shadcn/ui: - Add shadcn/ui primitives (Button, Card, Dialog, Input, etc.) - Replace custom styles with Tailwind CSS v4 theme configuration - Remove custom-theme.css in favor of globals.css with @theme directive Fix scroll overflow issues in multiple components: - ProjectSelector: "New Project" button no longer overlays project list - FolderBrowser: folder list now scrolls properly within modal - AgentCard: log modal content stays within bounds - ConversationHistory: conversation list scrolls correctly - KanbanColumn: feature cards scroll within fixed height - ScheduleModal: schedule form content scrolls properly Key technical changes: - Replace ScrollArea component with native overflow-y-auto divs - Add min-h-0 to flex containers to allow proper shrinking - Restructure dropdown layouts with flex-col for fixed footers New files: - ui/components.json (shadcn/ui configuration) - ui/src/components/ui/* (20 UI primitive components) - ui/src/lib/utils.ts (cn utility for class merging) - ui/tsconfig.app.json (app-specific TypeScript config) Co-Authored-By: Claude Opus 4.5 --- ui/components.json | 22 + ui/package-lock.json | 1025 ++++++++++++---- ui/package.json | 55 +- ui/src/App.tsx | 71 +- ui/src/components/ActivityFeed.tsx | 57 +- ui/src/components/AddFeatureForm.tsx | 159 ++- ui/src/components/AgentAvatar.tsx | 2 +- ui/src/components/AgentCard.tsx | 206 ++-- ui/src/components/AgentControl.tsx | 72 +- ui/src/components/AgentMissionControl.tsx | 48 +- ui/src/components/AgentThought.tsx | 33 +- ui/src/components/AssistantChat.tsx | 76 +- ui/src/components/AssistantFAB.tsx | 19 +- ui/src/components/AssistantPanel.tsx | 33 +- ui/src/components/CelebrationOverlay.tsx | 28 +- ui/src/components/ChatMessage.tsx | 92 +- ui/src/components/ConfirmDialog.tsx | 100 +- ui/src/components/ConversationHistory.tsx | 127 +- ui/src/components/DebugLogViewer.tsx | 116 +- ui/src/components/DependencyBadge.tsx | 28 +- ui/src/components/DependencyGraph.tsx | 150 ++- ui/src/components/DevServerControl.tsx | 53 +- ui/src/components/EditFeatureForm.tsx | 159 ++- ui/src/components/ExpandProjectChat.tsx | 118 +- ui/src/components/ExpandProjectModal.tsx | 2 +- ui/src/components/FeatureCard.tsx | 166 +-- ui/src/components/FeatureModal.tsx | 282 ++--- ui/src/components/FolderBrowser.tsx | 281 ++--- ui/src/components/KanbanBoard.tsx | 23 +- ui/src/components/KanbanColumn.tsx | 158 +-- ui/src/components/KeyboardShortcutsHelp.tsx | 70 +- ui/src/components/NewProjectModal.tsx | 319 +++-- ui/src/components/OrchestratorStatusCard.tsx | 177 +-- ui/src/components/ProgressDashboard.tsx | 102 +- ui/src/components/ProjectSelector.tsx | 173 ++- ui/src/components/QuestionOptions.tsx | 265 ++-- ui/src/components/ScheduleModal.tsx | 544 ++++----- ui/src/components/SettingsModal.tsx | 231 ++-- ui/src/components/SetupWizard.tsx | 197 +-- ui/src/components/SpecCreationChat.tsx | 163 +-- ui/src/components/Terminal.tsx | 48 +- ui/src/components/TerminalTabs.tsx | 30 +- ui/src/components/TypingIndicator.tsx | 9 +- ui/src/components/ViewToggle.tsx | 33 +- ui/src/components/ui/alert.tsx | 66 + ui/src/components/ui/badge.tsx | 48 + ui/src/components/ui/button.tsx | 64 + ui/src/components/ui/card.tsx | 92 ++ ui/src/components/ui/checkbox.tsx | 30 + ui/src/components/ui/dialog.tsx | 156 +++ ui/src/components/ui/dropdown-menu.tsx | 257 ++++ ui/src/components/ui/input.tsx | 21 + ui/src/components/ui/label.tsx | 22 + ui/src/components/ui/popover.tsx | 87 ++ ui/src/components/ui/radio-group.tsx | 45 + ui/src/components/ui/scroll-area.tsx | 56 + ui/src/components/ui/select.tsx | 190 +++ ui/src/components/ui/separator.tsx | 28 + ui/src/components/ui/switch.tsx | 35 + ui/src/components/ui/tabs.tsx | 89 ++ ui/src/components/ui/textarea.tsx | 18 + ui/src/components/ui/toggle.tsx | 47 + ui/src/components/ui/tooltip.tsx | 61 + ui/src/lib/utils.ts | 6 + ui/src/main.tsx | 2 +- ui/src/styles/custom-theme.css | 436 ------- ui/src/styles/globals.css | 1146 ++++-------------- ui/tsconfig.app.json | 30 + ui/tsconfig.json | 33 +- 69 files changed, 4900 insertions(+), 4287 deletions(-) create mode 100644 ui/components.json create mode 100644 ui/src/components/ui/alert.tsx create mode 100644 ui/src/components/ui/badge.tsx create mode 100644 ui/src/components/ui/button.tsx create mode 100644 ui/src/components/ui/card.tsx create mode 100644 ui/src/components/ui/checkbox.tsx create mode 100644 ui/src/components/ui/dialog.tsx create mode 100644 ui/src/components/ui/dropdown-menu.tsx create mode 100644 ui/src/components/ui/input.tsx create mode 100644 ui/src/components/ui/label.tsx create mode 100644 ui/src/components/ui/popover.tsx create mode 100644 ui/src/components/ui/radio-group.tsx create mode 100644 ui/src/components/ui/scroll-area.tsx create mode 100644 ui/src/components/ui/select.tsx create mode 100644 ui/src/components/ui/separator.tsx create mode 100644 ui/src/components/ui/switch.tsx create mode 100644 ui/src/components/ui/tabs.tsx create mode 100644 ui/src/components/ui/textarea.tsx create mode 100644 ui/src/components/ui/toggle.tsx create mode 100644 ui/src/components/ui/tooltip.tsx create mode 100644 ui/src/lib/utils.ts delete mode 100644 ui/src/styles/custom-theme.css create mode 100644 ui/tsconfig.app.json diff --git a/ui/components.json b/ui/components.json new file mode 100644 index 0000000..0b6231b --- /dev/null +++ b/ui/components.json @@ -0,0 +1,22 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "registries": {} +} diff --git a/ui/package-lock.json b/ui/package-lock.json index 6784b9f..b9af1ec 100644 --- a/ui/package-lock.json +++ b/ui/package-lock.json @@ -8,38 +8,53 @@ "name": "autocoder", "version": "1.0.0", "dependencies": { - "@radix-ui/react-dialog": "^1.1.2", - "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-tooltip": "^1.1.3", - "@tanstack/react-query": "^5.60.0", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-query": "^5.72.0", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-web-links": "^0.12.0", "@xterm/xterm": "^6.0.0", "@xyflow/react": "^12.10.0", "canvas-confetti": "^1.9.4", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dagre": "^0.8.5", - "lucide-react": "^0.460.0", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "lucide-react": "^0.475.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwind-merge": "^3.4.0" }, "devDependencies": { - "@eslint/js": "^9.13.0", + "@eslint/js": "^9.19.0", "@playwright/test": "^1.57.0", - "@tailwindcss/vite": "^4.0.0-beta.4", + "@tailwindcss/vite": "^4.1.0", "@types/canvas-confetti": "^1.9.0", "@types/dagre": "^0.7.53", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.3", - "eslint": "^9.13.0", - "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.14", - "globals": "^15.11.0", - "tailwindcss": "^4.0.0-beta.4", - "typescript": "~5.6.2", - "typescript-eslint": "^8.11.0", - "vite": "^5.4.10" + "@types/node": "^22.12.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.4.0", + "eslint": "^9.19.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.14.0", + "tailwindcss": "^4.1.0", + "tw-animate-css": "^1.4.0", + "typescript": "~5.7.3", + "typescript-eslint": "^8.23.0", + "vite": "^7.3.0" } }, "node_modules/@babel/code-frame": { @@ -325,9 +340,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.2.tgz", + "integrity": "sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==", "cpu": [ "ppc64" ], @@ -338,13 +353,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.2.tgz", + "integrity": "sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==", "cpu": [ "arm" ], @@ -355,13 +370,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.2.tgz", + "integrity": "sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==", "cpu": [ "arm64" ], @@ -372,13 +387,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.2.tgz", + "integrity": "sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==", "cpu": [ "x64" ], @@ -389,13 +404,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.2.tgz", + "integrity": "sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==", "cpu": [ "arm64" ], @@ -406,13 +421,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.2.tgz", + "integrity": "sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==", "cpu": [ "x64" ], @@ -423,13 +438,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.2.tgz", + "integrity": "sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==", "cpu": [ "arm64" ], @@ -440,13 +455,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.2.tgz", + "integrity": "sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==", "cpu": [ "x64" ], @@ -457,13 +472,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.2.tgz", + "integrity": "sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==", "cpu": [ "arm" ], @@ -474,13 +489,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.2.tgz", + "integrity": "sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==", "cpu": [ "arm64" ], @@ -491,13 +506,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.2.tgz", + "integrity": "sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==", "cpu": [ "ia32" ], @@ -508,13 +523,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.2.tgz", + "integrity": "sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==", "cpu": [ "loong64" ], @@ -525,13 +540,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.2.tgz", + "integrity": "sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==", "cpu": [ "mips64el" ], @@ -542,13 +557,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.2.tgz", + "integrity": "sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==", "cpu": [ "ppc64" ], @@ -559,13 +574,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.2.tgz", + "integrity": "sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==", "cpu": [ "riscv64" ], @@ -576,13 +591,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.2.tgz", + "integrity": "sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==", "cpu": [ "s390x" ], @@ -593,13 +608,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.2.tgz", + "integrity": "sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==", "cpu": [ "x64" ], @@ -610,13 +625,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.2.tgz", + "integrity": "sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.2.tgz", + "integrity": "sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==", "cpu": [ "x64" ], @@ -627,13 +659,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.2.tgz", + "integrity": "sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.2.tgz", + "integrity": "sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==", "cpu": [ "x64" ], @@ -644,13 +693,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.2.tgz", + "integrity": "sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.2.tgz", + "integrity": "sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==", "cpu": [ "x64" ], @@ -661,13 +727,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.2.tgz", + "integrity": "sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==", "cpu": [ "arm64" ], @@ -678,13 +744,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.2.tgz", + "integrity": "sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==", "cpu": [ "ia32" ], @@ -695,13 +761,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.2.tgz", + "integrity": "sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==", "cpu": [ "x64" ], @@ -712,7 +778,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@eslint-community/eslint-utils": { @@ -1027,6 +1093,12 @@ "node": ">=18" } }, + "node_modules/@radix-ui/number": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", + "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==", + "license": "MIT" + }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", @@ -1056,6 +1128,36 @@ } } }, + "node_modules/@radix-ui/react-checkbox": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz", + "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", @@ -1082,6 +1184,24 @@ } } }, + "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz", @@ -1148,6 +1268,24 @@ } } }, + "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", @@ -1277,6 +1415,52 @@ } } }, + "node_modules/@radix-ui/react-label": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz", + "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-menu": { "version": "2.1.16", "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz", @@ -1317,6 +1501,79 @@ } } }, + "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz", + "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-popper": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz", @@ -1420,6 +1677,56 @@ } } }, + "node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-radio-group": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-radio-group/-/react-radio-group-1.3.8.tgz", + "integrity": "sha512-VBKYIYImA5zsxACdisNQ3BjCBfmbGH3kQlnFVqlWU4tXwjy7cGX8ta80BcrO+WJXIn5iBylEH3K6ZTlee//lgQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", @@ -1451,7 +1758,81 @@ } } }, - "node_modules/@radix-ui/react-slot": { + "node_modules/@radix-ui/react-scroll-area": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.10.tgz", + "integrity": "sha512-tAXIa1g3sM5CGpVT0uIbUx/U3Gs5N8T52IICuCtObaos1S8fzsrPXG5WObkQN3S6NVl6wKgPhAIiBGbWnvc97A==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-layout-effect": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz", + "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/number": "1.1.1", + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-collection": "1.1.7", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-dismissable-layer": "1.1.11", + "@radix-ui/react-focus-guards": "1.1.3", + "@radix-ui/react-focus-scope": "1.1.7", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-popper": "1.2.8", + "@radix-ui/react-portal": "1.1.9", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-slot": "1.2.3", + "@radix-ui/react-use-callback-ref": "1.1.1", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-layout-effect": "1.1.1", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-visually-hidden": "1.2.3", + "aria-hidden": "^1.2.4", + "react-remove-scroll": "^2.6.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", @@ -1469,6 +1850,154 @@ } } }, + "node_modules/@radix-ui/react-separator": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz", + "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-primitive": "2.1.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-separator/node_modules/@radix-ui/react-primitive": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz", + "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-slot": "1.2.4" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-slot": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz", + "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-switch": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz", + "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-compose-refs": "1.1.2", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2", + "@radix-ui/react-use-previous": "1.1.1", + "@radix-ui/react-use-size": "1.1.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-tabs": { + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", + "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-context": "1.1.2", + "@radix-ui/react-direction": "1.1.1", + "@radix-ui/react-id": "1.1.1", + "@radix-ui/react-presence": "1.1.5", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-roving-focus": "1.1.11", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-toggle": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/@radix-ui/react-toggle/-/react-toggle-1.1.10.tgz", + "integrity": "sha512-lS1odchhFTeZv3xwHH31YPObmJn8gOg7Lq12inrr0+BH/l3Tsq32VfjqH1oh80ARM3mlkfMic15n0kg4sD1poQ==", + "license": "MIT", + "dependencies": { + "@radix-ui/primitive": "1.1.3", + "@radix-ui/react-primitive": "2.1.3", + "@radix-ui/react-use-controllable-state": "1.2.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", + "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-tooltip": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/@radix-ui/react-tooltip/-/react-tooltip-1.2.8.tgz", @@ -1503,6 +2032,24 @@ } } }, + "node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", + "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", + "license": "MIT", + "dependencies": { + "@radix-ui/react-compose-refs": "1.1.2" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", @@ -1588,6 +2135,21 @@ } } }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz", + "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==", + "license": "MIT", + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-use-rect": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz", @@ -2448,32 +3010,34 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "devOptional": true, - "license": "MIT" - }, - "node_modules/@types/react": { - "version": "18.3.27", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.27.tgz", - "integrity": "sha512-cisd7gxkzjBKU2GgdYrTdtQx1SORymWyaAFhaxQPK9bYO9ot3Y5OikQRvY0VYQtvwjeQnizCINJAenh/V7MK2w==", - "devOptional": true, + "node_modules/@types/node": { + "version": "22.19.7", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.7.tgz", + "integrity": "sha512-MciR4AKGHWl7xwxkBa6xUGxQJ4VBOmPTF7sL+iGzuahOFaO0jHCsuEfS80pan1ef4gWId1oWOweIhrDEYLuaOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.2.9", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.9.tgz", + "integrity": "sha512-Lpo8kgb/igvMIPeNV2rsYKTgaORYdO1XGVZ4Qz3akwOj0ySGYMPlQWa8BaLn0G63D1aSaAQ5ldR06wCpChQCjA==", + "dev": true, "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", - "devOptional": true, + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", + "dev": true, "license": "MIT", "peerDependencies": { - "@types/react": "^18.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@typescript-eslint/eslint-plugin": { @@ -3014,6 +3578,18 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/class-variance-authority": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz", + "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==", + "license": "Apache-2.0", + "dependencies": { + "clsx": "^2.1.1" + }, + "funding": { + "url": "https://polar.sh/cva" + } + }, "node_modules/classcat": { "version": "5.0.5", "resolved": "https://registry.npmjs.org/classcat/-/classcat-5.0.5.tgz", @@ -3082,7 +3658,7 @@ "version": "3.2.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", - "devOptional": true, + "dev": true, "license": "MIT" }, "node_modules/d3-color": { @@ -3263,9 +3839,9 @@ } }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.27.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.2.tgz", + "integrity": "sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -3273,32 +3849,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.27.2", + "@esbuild/android-arm": "0.27.2", + "@esbuild/android-arm64": "0.27.2", + "@esbuild/android-x64": "0.27.2", + "@esbuild/darwin-arm64": "0.27.2", + "@esbuild/darwin-x64": "0.27.2", + "@esbuild/freebsd-arm64": "0.27.2", + "@esbuild/freebsd-x64": "0.27.2", + "@esbuild/linux-arm": "0.27.2", + "@esbuild/linux-arm64": "0.27.2", + "@esbuild/linux-ia32": "0.27.2", + "@esbuild/linux-loong64": "0.27.2", + "@esbuild/linux-mips64el": "0.27.2", + "@esbuild/linux-ppc64": "0.27.2", + "@esbuild/linux-riscv64": "0.27.2", + "@esbuild/linux-s390x": "0.27.2", + "@esbuild/linux-x64": "0.27.2", + "@esbuild/netbsd-arm64": "0.27.2", + "@esbuild/netbsd-x64": "0.27.2", + "@esbuild/openbsd-arm64": "0.27.2", + "@esbuild/openbsd-x64": "0.27.2", + "@esbuild/openharmony-arm64": "0.27.2", + "@esbuild/sunos-x64": "0.27.2", + "@esbuild/win32-arm64": "0.27.2", + "@esbuild/win32-ia32": "0.27.2", + "@esbuild/win32-x64": "0.27.2" } }, "node_modules/escalade": { @@ -3758,6 +4337,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -4134,18 +4714,6 @@ "dev": true, "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -4157,12 +4725,12 @@ } }, "node_modules/lucide-react": { - "version": "0.460.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.460.0.tgz", - "integrity": "sha512-BVtq/DykVeIvRTJvRAgCsOwaGL8Un3Bxh8MbDxMhEWlZay3T4IpEKDEpwt5KZ0KJMHzgm6jrltxlT5eXOWXDHg==", + "version": "0.475.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.475.0.tgz", + "integrity": "sha512-NJzvVu1HwFVeZ+Gwq2q00KygM1aBhy/ZrhY9FsAgJtpB+E4R7uxRk9M2iKvHa6/vNxZydIB59htha4c2vvwvVg==", "license": "ISC", "peerDependencies": { - "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0-rc" + "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "node_modules/magic-string": { @@ -4425,28 +4993,24 @@ } }, "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", + "integrity": "sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.3" } }, "node_modules/react-refresh": { @@ -4581,13 +5145,10 @@ } }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -4658,6 +5219,16 @@ "node": ">=8" } }, + "node_modules/tailwind-merge": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz", + "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/dcastil" + } + }, "node_modules/tailwindcss": { "version": "4.1.18", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", @@ -4715,6 +5286,16 @@ "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "license": "0BSD" }, + "node_modules/tw-animate-css": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz", + "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/Wombosvideo" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4729,9 +5310,9 @@ } }, "node_modules/typescript": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", + "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -4766,6 +5347,13 @@ "typescript": ">=4.8.4 <6.0.0" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -4860,21 +5448,24 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz", + "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.27.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -4883,19 +5474,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", - "less": "*", + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.4.0" + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -4916,6 +5513,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, diff --git a/ui/package.json b/ui/package.json index 6226472..f70b9ca 100644 --- a/ui/package.json +++ b/ui/package.json @@ -12,37 +12,52 @@ "test:e2e:ui": "playwright test --ui" }, "dependencies": { - "@radix-ui/react-dialog": "^1.1.2", - "@radix-ui/react-dropdown-menu": "^2.1.2", - "@radix-ui/react-tooltip": "^1.1.3", - "@tanstack/react-query": "^5.60.0", + "@radix-ui/react-checkbox": "^1.3.3", + "@radix-ui/react-dialog": "^1.1.15", + "@radix-ui/react-dropdown-menu": "^2.1.16", + "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-popover": "^1.1.15", + "@radix-ui/react-radio-group": "^1.3.8", + "@radix-ui/react-scroll-area": "^1.2.10", + "@radix-ui/react-select": "^2.2.6", + "@radix-ui/react-separator": "^1.1.8", + "@radix-ui/react-slot": "^1.2.4", + "@radix-ui/react-switch": "^1.2.6", + "@radix-ui/react-tabs": "^1.1.13", + "@radix-ui/react-toggle": "^1.1.10", + "@radix-ui/react-tooltip": "^1.2.8", + "@tanstack/react-query": "^5.72.0", "@xterm/addon-fit": "^0.11.0", "@xterm/addon-web-links": "^0.12.0", "@xterm/xterm": "^6.0.0", "@xyflow/react": "^12.10.0", "canvas-confetti": "^1.9.4", + "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", "dagre": "^0.8.5", - "lucide-react": "^0.460.0", - "react": "^18.3.1", - "react-dom": "^18.3.1" + "lucide-react": "^0.475.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "tailwind-merge": "^3.4.0" }, "devDependencies": { - "@eslint/js": "^9.13.0", + "@eslint/js": "^9.19.0", "@playwright/test": "^1.57.0", - "@tailwindcss/vite": "^4.0.0-beta.4", + "@tailwindcss/vite": "^4.1.0", "@types/canvas-confetti": "^1.9.0", "@types/dagre": "^0.7.53", - "@types/react": "^18.3.12", - "@types/react-dom": "^18.3.1", - "@vitejs/plugin-react": "^4.3.3", - "eslint": "^9.13.0", - "eslint-plugin-react-hooks": "^5.0.0", - "eslint-plugin-react-refresh": "^0.4.14", - "globals": "^15.11.0", - "tailwindcss": "^4.0.0-beta.4", - "typescript": "~5.6.2", - "typescript-eslint": "^8.11.0", - "vite": "^5.4.10" + "@types/node": "^22.12.0", + "@types/react": "^19.0.0", + "@types/react-dom": "^19.0.0", + "@vitejs/plugin-react": "^4.4.0", + "eslint": "^9.19.0", + "eslint-plugin-react-hooks": "^5.1.0", + "eslint-plugin-react-refresh": "^0.4.19", + "globals": "^15.14.0", + "tailwindcss": "^4.1.0", + "tw-animate-css": "^1.4.0", + "typescript": "~5.7.3", + "typescript-eslint": "^8.23.0", + "vite": "^7.3.0" } } diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 0483ab7..5794285 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -27,6 +27,9 @@ import { KeyboardShortcutsHelp } from './components/KeyboardShortcutsHelp' import { getDependencyGraph } from './lib/api' import { Loader2, Settings, Moon, Sun } from 'lucide-react' import type { Feature } from './lib/types' +import { Button } from '@/components/ui/button' +import { Card, CardContent } from '@/components/ui/card' +import { Badge } from '@/components/ui/badge' const STORAGE_KEY = 'autocoder-selected-project' const DARK_MODE_KEY = 'autocoder-dark-mode' @@ -256,9 +259,9 @@ function App() { } return ( -
+
{/* Header */} -
+
{/* Logo and Title */} @@ -289,47 +292,49 @@ function App() { url={wsState.devServerUrl} /> - + {/* Ollama Mode Indicator */} {settings?.ollama_mode && (
Ollama - Ollama + Ollama
)} {/* GLM Mode Badge */} {settings?.glm_mode && ( - GLM - + )} )} {/* Dark mode toggle - always visible */} - +
@@ -341,11 +346,11 @@ function App() { style={{ paddingBottom: debugOpen ? debugPanelHeight + 32 : undefined }} > {!selectedProject ? ( -
+

Welcome to AutoCoder

-

+

Select a project from the dropdown above or create a new one to get started.

@@ -381,15 +386,17 @@ function App() { features.in_progress.length === 0 && features.done.length === 0 && wsState.agentStatus === 'running' && ( -
- -

- Initializing Features... -

-

- The agent is reading your spec and creating features. This may take a moment. -

-
+ + + +

+ Initializing Features... +

+

+ The agent is reading your spec and creating features. This may take a moment. +

+
+
)} {/* View Toggle - only show when there are features */} @@ -411,7 +418,7 @@ function App() { hasSpec={hasSpec} /> ) : ( -
+ {graphData ? ( ) : (
- +
)} -
+ )}
)} @@ -461,7 +468,7 @@ function App() { {/* Spec Creation Chat - for creating spec from empty kanban */} {showSpecChat && selectedProject && ( -
+
{ @@ -508,14 +515,10 @@ function App() { )} {/* Settings Modal */} - {showSettings && ( - setShowSettings(false)} /> - )} + setShowSettings(false)} /> {/* Keyboard Shortcuts Help */} - {showKeyboardHelp && ( - setShowKeyboardHelp(false)} /> - )} + setShowKeyboardHelp(false)} /> {/* Celebration Overlay - shows when a feature is completed by an agent */} {wsState.celebration && ( diff --git a/ui/src/components/ActivityFeed.tsx b/ui/src/components/ActivityFeed.tsx index 46a695b..23c64e9 100644 --- a/ui/src/components/ActivityFeed.tsx +++ b/ui/src/components/ActivityFeed.tsx @@ -1,6 +1,7 @@ import { Activity } from 'lucide-react' import { AgentAvatar } from './AgentAvatar' import type { AgentMascot } from '../lib/types' +import { Card, CardContent } from '@/components/ui/card' interface ActivityItem { agentName: string @@ -38,8 +39,8 @@ export function ActivityFeed({ activities, maxItems = 5, showHeader = true }: Ac
{showHeader && (
- - + + Recent Activity
@@ -47,34 +48,36 @@ export function ActivityFeed({ activities, maxItems = 5, showHeader = true }: Ac
{displayedActivities.map((activity) => ( -
- -
-
- - {activity.agentName} - - - #{activity.featureId} - - - {formatTimestamp(activity.timestamp)} - + + +
+
+ + {activity.agentName} + + + #{activity.featureId} + + + {formatTimestamp(activity.timestamp)} + +
+

+ {activity.thought} +

-

- {activity.thought} -

-
-
+ + ))}
diff --git a/ui/src/components/AddFeatureForm.tsx b/ui/src/components/AddFeatureForm.tsx index 834022c..529b801 100644 --- a/ui/src/components/AddFeatureForm.tsx +++ b/ui/src/components/AddFeatureForm.tsx @@ -1,6 +1,18 @@ import { useState, useId } from 'react' import { X, Plus, Trash2, Loader2, AlertCircle } from 'lucide-react' import { useCreateFeature } from '../hooks/useProjects' +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, + DialogFooter, +} from '@/components/ui/dialog' +import { Button } from '@/components/ui/button' +import { Input } from '@/components/ui/input' +import { Textarea } from '@/components/ui/textarea' +import { Label } from '@/components/ui/label' +import { Alert, AlertDescription } from '@/components/ui/alert' interface Step { id: string @@ -65,149 +77,135 @@ export function AddFeatureForm({ projectName, onClose }: AddFeatureFormProps) { const isValid = category.trim() && name.trim() && description.trim() return ( -
-
e.stopPropagation()} - > - {/* Header */} -
-

- Add Feature -

- -
+ !open && onClose()}> + + + Add Feature + - {/* Form */} -
+ {/* Error Message */} {error && ( -
- - {error} - -
+ + + + {error} + + + )} {/* Category & Priority Row */}
-
- - + + setCategory(e.target.value)} placeholder="e.g., Authentication, UI, API" - className="neo-input" required />
-
- - + + setPriority(e.target.value)} placeholder="Auto" min="1" - className="neo-input" />
{/* Name */} -
- - + + setName(e.target.value)} placeholder="e.g., User login form" - className="neo-input" required />
{/* Description */} -
- -