feat: add headless browser toggle to settings UI

Replace the PLAYWRIGHT_HEADLESS environment variable with a global
setting toggle in the Settings modal. The setting is persisted in the
registry DB and injected as an env var into agent subprocesses, so
client.py reads it unchanged.

Backend:
- Add playwright_headless field to SettingsResponse/SettingsUpdate schemas
- Read/write the setting in settings router via existing _parse_bool helper
- Pass playwright_headless from agent router through to process manager
- Inject PLAYWRIGHT_HEADLESS env var into subprocess environment

Frontend:
- Add playwright_headless to Settings/SettingsUpdate TypeScript types
- Add "Headless Browser" Switch toggle below YOLO mode in SettingsModal
- Add default value to DEFAULT_SETTINGS in useProjects

Also fix CSS build warning: change @import url("tw-animate-css") to bare
@import "tw-animate-css" so Tailwind v4 inlines it during compilation
instead of leaving it for Vite/Lightning CSS post-processing.

Remove stale summary.md from previous refactoring session.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Auto
2026-02-01 13:40:46 +02:00
parent 94e0b05cb1
commit 24481d474d
9 changed files with 39 additions and 152 deletions

View File

@@ -17,11 +17,11 @@ from ..utils.project_helpers import get_project_path as _get_project_path
from ..utils.validation import validate_project_name
def _get_settings_defaults() -> tuple[bool, str, int]:
def _get_settings_defaults() -> tuple[bool, str, int, bool]:
"""Get defaults from global settings.
Returns:
Tuple of (yolo_mode, model, testing_agent_ratio)
Tuple of (yolo_mode, model, testing_agent_ratio, playwright_headless)
"""
import sys
root = Path(__file__).parent.parent.parent
@@ -40,7 +40,9 @@ def _get_settings_defaults() -> tuple[bool, str, int]:
except (ValueError, TypeError):
testing_agent_ratio = 1
return yolo_mode, model, testing_agent_ratio
playwright_headless = (settings.get("playwright_headless") or "true").lower() == "true"
return yolo_mode, model, testing_agent_ratio, playwright_headless
router = APIRouter(prefix="/api/projects/{project_name}/agent", tags=["agent"])
@@ -89,7 +91,7 @@ async def start_agent(
manager = get_project_manager(project_name)
# Get defaults from global settings if not provided in request
default_yolo, default_model, default_testing_ratio = _get_settings_defaults()
default_yolo, default_model, default_testing_ratio, playwright_headless = _get_settings_defaults()
yolo_mode = request.yolo_mode if request.yolo_mode is not None else default_yolo
model = request.model if request.model else default_model
@@ -101,6 +103,7 @@ async def start_agent(
model=model,
max_concurrency=max_concurrency,
testing_agent_ratio=testing_agent_ratio,
playwright_headless=playwright_headless,
)
# Notify scheduler of manual start (to prevent auto-stop during scheduled window)

View File

@@ -91,6 +91,7 @@ async def get_settings():
glm_mode=_is_glm_mode(),
ollama_mode=_is_ollama_mode(),
testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1),
playwright_headless=_parse_bool(all_settings.get("playwright_headless"), default=True),
)
@@ -106,6 +107,9 @@ async def update_settings(update: SettingsUpdate):
if update.testing_agent_ratio is not None:
set_setting("testing_agent_ratio", str(update.testing_agent_ratio))
if update.playwright_headless is not None:
set_setting("playwright_headless", "true" if update.playwright_headless else "false")
# Return updated settings
all_settings = get_all_settings()
return SettingsResponse(
@@ -114,4 +118,5 @@ async def update_settings(update: SettingsUpdate):
glm_mode=_is_glm_mode(),
ollama_mode=_is_ollama_mode(),
testing_agent_ratio=_parse_int(all_settings.get("testing_agent_ratio"), 1),
playwright_headless=_parse_bool(all_settings.get("playwright_headless"), default=True),
)

View File

@@ -398,6 +398,7 @@ class SettingsResponse(BaseModel):
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)
playwright_headless: bool = True
class ModelsResponse(BaseModel):
@@ -411,6 +412,7 @@ class SettingsUpdate(BaseModel):
yolo_mode: bool | None = None
model: str | None = None
testing_agent_ratio: int | None = None # 0-3
playwright_headless: bool | None = None
@field_validator('model')
@classmethod

View File

@@ -297,6 +297,7 @@ class AgentProcessManager:
parallel_mode: bool = False,
max_concurrency: int | None = None,
testing_agent_ratio: int = 1,
playwright_headless: bool = True,
) -> tuple[bool, str]:
"""
Start the agent as a subprocess.
@@ -307,6 +308,7 @@ class AgentProcessManager:
parallel_mode: DEPRECATED - ignored, always uses unified orchestrator
max_concurrency: Max concurrent coding agents (1-5, default 1)
testing_agent_ratio: Number of regression testing agents (0-3, default 1)
playwright_headless: If True, run browser in headless mode
Returns:
Tuple of (success, message)
@@ -358,7 +360,7 @@ class AgentProcessManager:
"stdout": subprocess.PIPE,
"stderr": subprocess.STDOUT,
"cwd": str(self.project_dir),
"env": {**os.environ, "PYTHONUNBUFFERED": "1"},
"env": {**os.environ, "PYTHONUNBUFFERED": "1", "PLAYWRIGHT_HEADLESS": "true" if playwright_headless else "false"},
}
if sys.platform == "win32":
popen_kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW