From e833461898d8703916f9cb35354de9a032b0e280 Mon Sep 17 00:00:00 2001 From: Jacobcdsmith <88069592+Jacobcdsmith@users.noreply.github.com> Date: Thu, 13 Aug 2026 15:04:13 +0000 Subject: [PATCH] feat: add canvas theme customizer to visual agent builder This change introduces 4 beautiful visual themes for the canvas workspace (Default Ice, Retro Amber, Blueprint Grid, and Minimal Ink) defined with HSL variables in CSS. It adds a dropdown selector in the header toolbar, manages the selection via state, applies theme classes dynamically, persists theme selections to localStorage, and includes comprehensive unit testing. --- dev_server.log | 2 +- frontend/src/index.css | 76 +++++++++++++++++++++++++ frontend/src/pages/Index.tsx | 34 +++++++++++ frontend/src/test/canvasThemes.test.tsx | 65 +++++++++++++++++++++ 4 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 frontend/src/test/canvasThemes.test.tsx diff --git a/dev_server.log b/dev_server.log index 9f3401e..7429db7 100644 --- a/dev_server.log +++ b/dev_server.log @@ -1,6 +1,6 @@ $ vite --host 0.0.0.0 --port 3000 - VITE v5.4.21 ready in 312 ms + VITE v5.4.21 ready in 298 ms โžœ Local: http://localhost:3000/ โžœ Network: http://192.168.0.2:3000/ diff --git a/frontend/src/index.css b/frontend/src/index.css index 9ae2839..3811087 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -80,6 +80,82 @@ All colors MUST be HSL. --sidebar-ring: 217.2 91.2% 59.8%; } + .theme-ice { + --background: 205 60% 97%; + --foreground: 215 60% 14%; + --paper: 205 60% 97%; + --ink: 215 80% 22%; + --ink-soft: 215 50% 38%; + --ink-faint: 210 35% 65%; + --grid-line: 210 50% 85%; + --accent-cyan: 200 90% 45%; + --accent-deep: 220 85% 35%; + --gradient-paper: linear-gradient(180deg, hsl(200 70% 98%) 0%, hsl(210 60% 94%) 100%); + --gradient-header: linear-gradient(90deg, hsl(210 80% 96%) 0%, hsl(195 85% 92%) 50%, hsl(210 80% 96%) 100%); + --gradient-node: linear-gradient(180deg, hsl(0 0% 100%) 0%, hsl(205 60% 96%) 100%); + --gradient-accent: linear-gradient(135deg, hsl(200 90% 50%) 0%, hsl(220 85% 40%) 100%); + --edge-selected: 200 95% 45%; + --border: 214.3 31.8% 91.4%; + --input: 214.3 31.8% 91.4%; + } + + .theme-amber { + --background: 30 20% 8%; + --foreground: 35 100% 60%; + --paper: 30 20% 10%; + --ink: 35 100% 55%; + --ink-soft: 35 80% 45%; + --ink-faint: 35 50% 25%; + --grid-line: 35 30% 15%; + --accent-cyan: 35 100% 50%; + --accent-deep: 30 100% 40%; + --gradient-paper: linear-gradient(180deg, hsl(30 20% 6%) 0%, hsl(30 20% 10%) 100%); + --gradient-header: linear-gradient(90deg, hsl(30 25% 10%) 0%, hsl(30 20% 8%) 100%); + --gradient-node: linear-gradient(180deg, hsl(30 20% 10%) 0%, hsl(30 20% 13%) 100%); + --gradient-accent: linear-gradient(135deg, hsl(35 100% 60%) 0%, hsl(30 100% 40%) 100%); + --edge-selected: 35 100% 50%; + --border: 30 20% 20%; + --input: 30 20% 20%; + } + + .theme-blueprint { + --background: 220 80% 12%; + --foreground: 210 100% 95%; + --paper: 220 80% 15%; + --ink: 210 100% 95%; + --ink-soft: 210 70% 75%; + --ink-faint: 210 40% 45%; + --grid-line: 210 60% 30%; + --accent-cyan: 190 100% 60%; + --accent-deep: 220 90% 40%; + --gradient-paper: linear-gradient(180deg, hsl(220 80% 10%) 0%, hsl(220 80% 16%) 100%); + --gradient-header: linear-gradient(90deg, hsl(220 70% 16%) 0%, hsl(220 70% 20%) 100%); + --gradient-node: linear-gradient(180deg, hsl(220 60% 16%) 0%, hsl(220 50% 20%) 100%); + --gradient-accent: linear-gradient(135deg, hsl(190 100% 55%) 0%, hsl(220 90% 40%) 100%); + --edge-selected: 190 100% 60%; + --border: 220 50% 25%; + --input: 220 50% 25%; + } + + .theme-ink { + --background: 0 0% 100%; + --foreground: 0 0% 0%; + --paper: 0 0% 100%; + --ink: 0 0% 0%; + --ink-soft: 0 0% 20%; + --ink-faint: 0 0% 60%; + --grid-line: 0 0% 85%; + --accent-cyan: 0 0% 15%; + --accent-deep: 0 0% 0%; + --gradient-paper: linear-gradient(180deg, hsl(0 0% 100%) 0%, hsl(0 0% 96%) 100%); + --gradient-header: linear-gradient(90deg, hsl(0 0% 100%) 0%, hsl(0 0% 94%) 100%); + --gradient-node: linear-gradient(180deg, hsl(0 0% 100%) 0%, hsl(0 0% 98%) 100%); + --gradient-accent: linear-gradient(135deg, hsl(0 0% 20%) 0%, hsl(0 0% 0%) 100%); + --edge-selected: 0 0% 10%; + --border: 0 0% 80%; + --input: 0 0% 80%; + } + .dark { --background: 222.2 84% 4.9%; --foreground: 210 40% 98%; diff --git a/frontend/src/pages/Index.tsx b/frontend/src/pages/Index.tsx index f4206f9..9beb36c 100644 --- a/frontend/src/pages/Index.tsx +++ b/frontend/src/pages/Index.tsx @@ -175,6 +175,26 @@ function Canvas() { const rf = useReactFlow(); const isMobile = useIsMobile(); + // ---- Canvas Theme Selector State ---- + const [canvasTheme, setCanvasTheme] = useState(() => { + try { + return localStorage.getItem("agent_flow.canvas_theme") || "theme-ice"; + } catch { + return "theme-ice"; + } + }); + + useEffect(() => { + const root = document.documentElement; + root.classList.remove("theme-ice", "theme-amber", "theme-blueprint", "theme-ink"); + root.classList.add(canvasTheme); + try { + localStorage.setItem("agent_flow.canvas_theme", canvasTheme); + } catch { + // ignore + } + }, [canvasTheme]); + // ---- Workflows Library State ---- const [workflows, setWorkflows] = useState(() => loadWorkflows()); const [activeWorkflowId, setActiveWorkflowId] = useState(() => loadActiveWorkflowId()); @@ -1417,6 +1437,20 @@ function Canvas() { {nodes.length} nodes ยท {edges.length} edges +