diff --git a/.env.example b/.env.example index aa3d9fa..1718f6d 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 7994f64..80573a9 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,12 +47,27 @@ 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() - # 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: + """ + 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 @@ -228,10 +248,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/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..1d7a032 --- /dev/null +++ b/ui/src/styles/custom-theme.css @@ -0,0 +1,437 @@ +/* + * Clean Twitter-Style Theme + * ========================= + * Based on user's exact design system values + */ + +:root { + /* 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 { + /* 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; +} + +/* ===== GLOBAL OVERRIDES ===== */ + +* { + 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; +} + +/* ===== 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"] { + 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; +}