From 365c8bc22616ca5b7c27d7c366e8136dbf441d46 Mon Sep 17 00:00:00 2001 From: engkimo Date: Thu, 4 Jun 2026 23:37:16 +0900 Subject: [PATCH 1/2] style(ui): switch to a Claude-like warm beige light theme Flip the dark palette to a warm cream/beige light theme: background #f4f1ea, warm near-black text #33312b, surface #fbf9f3, border #e3ded2, terracotta accent #c15f3c, warmed status colors. Driven by globals.css :root vars + theme.ts tokens; drop the html.dark class; convert TaskGraph and code-block hardcoded dark hex to theme vars. Co-Authored-By: Claude Opus 4.8 --- ui/app/globals.css | 50 +++++++++++++++---------------- ui/app/layout.tsx | 2 +- ui/components/CodeBlock.tsx | 6 ++-- ui/components/ExecutionResult.tsx | 6 ++-- ui/components/TaskGraph.tsx | 18 +++++------ ui/lib/theme.ts | 32 ++++++++++---------- 6 files changed, 57 insertions(+), 57 deletions(-) diff --git a/ui/app/globals.css b/ui/app/globals.css index f299b65..925565c 100644 --- a/ui/app/globals.css +++ b/ui/app/globals.css @@ -1,34 +1,34 @@ @import "tailwindcss"; :root { - /* ── Existing Morphic custom vars ── */ - --background: #0a0a0f; - --foreground: #e2e8f0; - --surface: #12121a; - --border: #1e1e2e; - --accent: #6366f1; - --success: #10b981; - --warning: #f59e0b; - --danger: #ef4444; - --info: #38bdf8; - --local-free: #34d399; - --text-muted: #94a3b8; + /* ── Claude-like warm beige theme (light) ── */ + --background: #f4f1ea; /* warm cream canvas */ + --foreground: #33312b; /* warm near-black text (font) */ + --surface: #fbf9f3; /* lighter cream for cards/panels */ + --border: #e3ded2; /* warm light border */ + --accent: #c15f3c; /* Claude terracotta */ + --success: #4f7a52; + --warning: #b9821f; + --danger: #c0492f; + --info: #3b7a99; + --local-free: #4f7a52; + --text-muted: #7a776e; /* warm muted text */ - /* ── shadcn semantic vars (mapped to existing theme) ── */ - --card: #12121a; - --card-foreground: #e2e8f0; - --popover: #12121a; - --popover-foreground: #e2e8f0; - --primary: #6366f1; + /* ── shadcn semantic vars (mapped to the warm theme) ── */ + --card: #fbf9f3; + --card-foreground: #33312b; + --popover: #fbf9f3; + --popover-foreground: #33312b; + --primary: #c15f3c; --primary-foreground: #ffffff; - --secondary: #1e1e2e; - --secondary-foreground: #e2e8f0; - --muted: #12121a; - --muted-foreground: #94a3b8; - --destructive: #ef4444; + --secondary: #eae5d9; + --secondary-foreground: #33312b; + --muted: #efeae0; + --muted-foreground: #7a776e; + --destructive: #c0492f; --destructive-foreground: #ffffff; - --input: #1e1e2e; - --ring: #6366f1; + --input: #e3ded2; + --ring: #c15f3c; --radius: 0.5rem; } diff --git a/ui/app/layout.tsx b/ui/app/layout.tsx index bad0c4a..be76911 100644 --- a/ui/app/layout.tsx +++ b/ui/app/layout.tsx @@ -27,7 +27,7 @@ export default function RootLayout({ children: React.ReactNode; }>) { return ( - + diff --git a/ui/components/CodeBlock.tsx b/ui/components/CodeBlock.tsx index aa8598f..1234efe 100644 --- a/ui/components/CodeBlock.tsx +++ b/ui/components/CodeBlock.tsx @@ -19,9 +19,9 @@ export default function CodeBlock({ code, language }: CodeBlockProps) { }, [code]); return ( -
+
{/* Header: language label + copy button */} -
+
{language || "code"} @@ -43,7 +43,7 @@ export default function CodeBlock({ code, language }: CodeBlockProps) {
{/* Code content */}
-        {code}
+        {code}
       
); diff --git a/ui/components/ExecutionResult.tsx b/ui/components/ExecutionResult.tsx index 10e1861..840d42e 100644 --- a/ui/components/ExecutionResult.tsx +++ b/ui/components/ExecutionResult.tsx @@ -24,7 +24,7 @@ export default function ExecutionResult({ {/* Execution output */} {output && (
-
+
{success ? ( ) : ( @@ -35,8 +35,8 @@ export default function ExecutionResult({ Output
-
-            
+          
+            
               {output}
             
           
diff --git a/ui/components/TaskGraph.tsx b/ui/components/TaskGraph.tsx index 3d17d67..ce6ebf1 100644 --- a/ui/components/TaskGraph.tsx +++ b/ui/components/TaskGraph.tsx @@ -107,9 +107,9 @@ function SubTaskNode({ data }: NodeProps) { className={classNames} style={{ animationDelay: d.isNew ? `${d.enterDelay}ms` : undefined, - borderTop: d.selected ? `2px solid ${accentColor}` : `1px solid #1E1E2E`, - borderRight: d.selected ? `2px solid ${accentColor}` : `1px solid #1E1E2E`, - borderBottom: d.selected ? `2px solid ${accentColor}` : `1px solid #1E1E2E`, + borderTop: d.selected ? `2px solid ${accentColor}` : `1px solid #E3DED2`, + borderRight: d.selected ? `2px solid ${accentColor}` : `1px solid #E3DED2`, + borderBottom: d.selected ? `2px solid ${accentColor}` : `1px solid #E3DED2`, borderLeft: `4px solid ${accentColor}`, minWidth: 240, maxWidth: 320, @@ -373,9 +373,9 @@ export default function TaskGraph({ task }: TaskGraphProps) { data: { label: truncateWithEllipsis(task.goal, 30) }, className: isGoalNew ? "morphic-node-enter" : undefined, style: { - background: "#12121A", - color: "#E2E8F0", - border: `2px solid ${STATUS_COLORS[task.status] || "#6366F1"}`, + background: "#FBF9F3", + color: "#33312B", + border: `2px solid ${STATUS_COLORS[task.status] || "#C15F3C"}`, borderRadius: 8, padding: "8px 12px", fontWeight: 600, @@ -485,12 +485,12 @@ export default function TaskGraph({ task }: TaskGraphProps) { nodeTypes={nodeTypes} fitView proOptions={{ hideAttribution: true }} - style={{ background: "#0A0A0F" }} + style={{ background: "#F4F1EA" }} onPaneClick={() => setSelectedId(null)} > - +
diff --git a/ui/lib/theme.ts b/ui/lib/theme.ts index 54c6100..37f43f5 100644 --- a/ui/lib/theme.ts +++ b/ui/lib/theme.ts @@ -2,24 +2,24 @@ export const theme = { colors: { - background: "#0A0A0F", - surface: "#12121A", - border: "#1E1E2E", - accent: "#6366F1", - success: "#10B981", - warning: "#F59E0B", - danger: "#EF4444", - info: "#38BDF8", - localFree: "#34D399", - text: "#E2E8F0", - textMuted: "#94A3B8", + background: "#F4F1EA", + surface: "#FBF9F3", + border: "#E3DED2", + accent: "#C15F3C", + success: "#4F7A52", + warning: "#B9821F", + danger: "#C0492F", + info: "#3B7A99", + localFree: "#4F7A52", + text: "#33312B", + textMuted: "#7A776E", }, } as const; export const statusStyles: Record = { - pending: { border: "#2D2D42", icon: "\u23F3" }, - running: { border: "#38BDF8", icon: "\u26A1" }, - success: { border: "#10B981", icon: "\u2713" }, - failed: { border: "#EF4444", icon: "\u2717" }, - fallback: { border: "#F59E0B", icon: "\u21BB" }, + pending: { border: "#C8C2B2", icon: "\u23F3" }, + running: { border: "#3B7A99", icon: "\u26A1" }, + success: { border: "#4F7A52", icon: "\u2713" }, + failed: { border: "#C0492F", icon: "\u2717" }, + fallback: { border: "#B9821F", icon: "\u21BB" }, }; From 999bad15e14c810f2160f148a4c20316319b883a Mon Sep 17 00:00:00 2001 From: engkimo Date: Thu, 4 Jun 2026 23:37:16 +0900 Subject: [PATCH 2/2] fix(api): System Health reads agent_drivers, not engine_registry The /api/settings/health engine loop referenced container.engine_registry, which does not exist (drivers live in container.agent_drivers keyed by AgentEngineType). Every engine fell to status=skip, so the UI only showed ollama + database. Read agent_drivers and map name->AgentEngineType. Co-Authored-By: Claude Opus 4.8 --- interface/api/routes/settings.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/interface/api/routes/settings.py b/interface/api/routes/settings.py index 3c524ee..33f5376 100644 --- a/interface/api/routes/settings.py +++ b/interface/api/routes/settings.py @@ -130,18 +130,24 @@ async def get_health(request: Request) -> dict: db_ok = False checks.append({"name": "database", "status": "ok" if db_ok else "fail"}) - # Engine availability summary + # Engine availability summary. Drivers live in container.agent_drivers, + # keyed by AgentEngineType (NOT a non-existent `engine_registry`). + from domain.value_objects.agent_engine import AgentEngineType + + drivers = getattr(container, "agent_drivers", {}) or {} for engine_name in ["claude_code", "gemini_cli", "codex_cli", "openhands"]: - registry = getattr(container, "engine_registry", None) - driver = registry.get(engine_name) if registry else None - if driver: - try: - available = await driver.is_available() - checks.append({"name": engine_name, "status": "ok" if available else "warn"}) - except Exception: - checks.append({"name": engine_name, "status": "fail"}) - else: + try: + driver = drivers.get(AgentEngineType(engine_name)) + except ValueError: + driver = None + if driver is None: checks.append({"name": engine_name, "status": "skip"}) + continue + try: + available = await driver.is_available() + checks.append({"name": engine_name, "status": "ok" if available else "warn"}) + except Exception: + checks.append({"name": engine_name, "status": "fail"}) overall = "ok" if all(c["status"] in ("ok", "skip") for c in checks) else "degraded" return {"overall": overall, "checks": checks}