From a4b5dc1925cf68a6692edaf5cf84bbbdb23ade68 Mon Sep 17 00:00:00 2001 From: SirEdvin Date: Fri, 15 May 2026 12:43:52 +0000 Subject: [PATCH 1/5] feat(ui): add light theme toggle --- frontend/src/App.tsx | 21 ++-- frontend/src/components/nav/PageToolbar.tsx | 12 +-- .../src/components/nav/WorkspaceSwitcher.tsx | 2 +- .../src/components/sidebar/EdgeSidebar.tsx | 4 +- .../src/components/theme/ThemeProvider.tsx | 57 +++++++++++ frontend/src/components/theme/ThemeToggle.tsx | 43 ++++++++ .../src/components/theme/theme-context.ts | 23 +++++ frontend/src/components/ui/Avatar.tsx | 2 +- frontend/src/components/ui/Button.tsx | 2 +- frontend/src/index.css | 98 +++++++++++++++---- frontend/src/pages/OverviewPage.tsx | 2 +- frontend/src/pages/TechnologiesPage.tsx | 2 +- 12 files changed, 226 insertions(+), 42 deletions(-) create mode 100644 frontend/src/components/theme/ThemeProvider.tsx create mode 100644 frontend/src/components/theme/ThemeToggle.tsx create mode 100644 frontend/src/components/theme/theme-context.ts diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index 91c7aa0..ae26355 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -27,6 +27,7 @@ import { useAuthStore } from './stores/auth-store' import { useWorkspaceStore } from './stores/workspace-store' import { useWorkspaceSocket } from './hooks/use-realtime' import { ChatBubble } from './components/agent-chat/ChatBubble' +import { ThemeProvider } from './components/theme/ThemeProvider' import './index.css' const queryClient = new QueryClient({ @@ -74,10 +75,11 @@ function App() { return ( - {isAuthenticated && } - {isAuthenticated && workspaceId && } - - + + {isAuthenticated && } + {isAuthenticated && workspaceId && } + + : } @@ -213,11 +215,12 @@ function App() { : } /> - - {/* Agent chat bubble — floats over all workspace pages, outside route - layout but inside the Router so useNavigate() (in useViewChange) works. */} - {isAuthenticated && } - + + {/* Agent chat bubble — floats over all workspace pages, outside route + layout but inside the Router so useNavigate() (in useViewChange) works. */} + {isAuthenticated && } + + ) } diff --git a/frontend/src/components/nav/PageToolbar.tsx b/frontend/src/components/nav/PageToolbar.tsx index 65bc1aa..6bcfb76 100644 --- a/frontend/src/components/nav/PageToolbar.tsx +++ b/frontend/src/components/nav/PageToolbar.tsx @@ -1,6 +1,7 @@ import { useState } from 'react' import { Button } from '../ui/Button' import { Kbd } from '../ui/Kbd' +import { ThemeToggle } from '../theme/ThemeToggle' import { SearchModal } from './SearchModal' // ─── Types ────────────────────────────────────────────────────────────────── @@ -112,12 +113,11 @@ export function PageToolbar({ )} - {/* Right: actions slot */} - {actions && ( -
- {actions} -
- )} + {/* Right: global toolbar controls + page actions */} +
+ + {actions} +
) } diff --git a/frontend/src/components/nav/WorkspaceSwitcher.tsx b/frontend/src/components/nav/WorkspaceSwitcher.tsx index b721297..9b46961 100644 --- a/frontend/src/components/nav/WorkspaceSwitcher.tsx +++ b/frontend/src/components/nav/WorkspaceSwitcher.tsx @@ -128,7 +128,7 @@ export function WorkspaceSwitcher() { className="w-full flex items-center justify-between px-3 py-2 rounded-md border border-border-base bg-surface hover:bg-surface-hi transition-all duration-[120ms]" >
-
+
{wsInitials.slice(0, 1)}
diff --git a/frontend/src/components/sidebar/EdgeSidebar.tsx b/frontend/src/components/sidebar/EdgeSidebar.tsx index f3e2449..1f5ac32 100644 --- a/frontend/src/components/sidebar/EdgeSidebar.tsx +++ b/frontend/src/components/sidebar/EdgeSidebar.tsx @@ -140,7 +140,7 @@ export function EdgeSidebar({ diagramId }: EdgeSidebarProps) { className={cn( 'flex-1 flex flex-col items-center px-2 py-1 rounded text-[11px] transition-colors', conn.direction === d.value - ? 'bg-coral text-bg font-medium' + ? 'bg-coral text-on-accent font-medium' : 'text-text-3 hover:text-text-2', )} > @@ -173,7 +173,7 @@ export function EdgeSidebar({ diagramId }: EdgeSidebarProps) { className={cn( 'flex-1 flex flex-col items-center px-2 py-1 rounded text-[11px] transition-colors', conn.shape === s.value - ? 'bg-coral text-bg font-medium' + ? 'bg-coral text-on-accent font-medium' : 'text-text-3 hover:text-text-2', )} > diff --git a/frontend/src/components/theme/ThemeProvider.tsx b/frontend/src/components/theme/ThemeProvider.tsx new file mode 100644 index 0000000..4795b11 --- /dev/null +++ b/frontend/src/components/theme/ThemeProvider.tsx @@ -0,0 +1,57 @@ +import { useEffect, useState } from 'react' +import { ThemeContext, type ThemeMode } from './theme-context' + +const STORAGE_KEY = 'archflow.theme' +const DEFAULT_THEME: ThemeMode = 'dark' + +function isThemeMode(value: string | null): value is ThemeMode { + return value === 'dark' || value === 'light' +} + +function readStoredTheme(): ThemeMode { + if (typeof window === 'undefined') return DEFAULT_THEME + + try { + const stored = window.localStorage.getItem(STORAGE_KEY) + return isThemeMode(stored) ? stored : DEFAULT_THEME + } catch { + return DEFAULT_THEME + } +} + +function applyTheme(theme: ThemeMode) { + if (typeof document === 'undefined') return + document.documentElement.dataset.theme = theme + document.documentElement.classList.toggle('light', theme === 'light') + document.documentElement.classList.toggle('dark', theme === 'dark') +} + +applyTheme(readStoredTheme()) + +export function ThemeProvider({ children }: { children: React.ReactNode }) { + const [theme, setThemeState] = useState(readStoredTheme) + + const setTheme = (nextTheme: ThemeMode) => { + setThemeState(nextTheme) + try { + window.localStorage.setItem(STORAGE_KEY, nextTheme) + } catch { + // Keep the in-session theme even if storage is unavailable. + } + applyTheme(nextTheme) + } + + const toggleTheme = () => { + setTheme(theme === 'dark' ? 'light' : 'dark') + } + + useEffect(() => { + applyTheme(theme) + }, [theme]) + + return ( + + {children} + + ) +} diff --git a/frontend/src/components/theme/ThemeToggle.tsx b/frontend/src/components/theme/ThemeToggle.tsx new file mode 100644 index 0000000..2f7c176 --- /dev/null +++ b/frontend/src/components/theme/ThemeToggle.tsx @@ -0,0 +1,43 @@ +import { Button } from '../ui/Button' +import { useOptionalTheme } from './theme-context' + +function SunIcon() { + return ( + + ) +} + +function MoonIcon() { + return ( + + ) +} + +export function ThemeToggle() { + const themeContext = useOptionalTheme() + if (themeContext == null) return null + + const { theme, toggleTheme } = themeContext + const isDark = theme === 'dark' + const nextThemeLabel = isDark ? 'light' : 'dark' + + return ( + + ) +} diff --git a/frontend/src/components/theme/theme-context.ts b/frontend/src/components/theme/theme-context.ts new file mode 100644 index 0000000..4959ec8 --- /dev/null +++ b/frontend/src/components/theme/theme-context.ts @@ -0,0 +1,23 @@ +import { createContext, useContext } from 'react' + +export type ThemeMode = 'dark' | 'light' + +export interface ThemeContextValue { + theme: ThemeMode + setTheme: (theme: ThemeMode) => void + toggleTheme: () => void +} + +export const ThemeContext = createContext(null) + +export function useOptionalTheme() { + return useContext(ThemeContext) +} + +export function useTheme() { + const context = useOptionalTheme() + if (context == null) { + throw new Error('useTheme must be used within ThemeProvider') + } + return context +} diff --git a/frontend/src/components/ui/Avatar.tsx b/frontend/src/components/ui/Avatar.tsx index c925518..bfa98f6 100644 --- a/frontend/src/components/ui/Avatar.tsx +++ b/frontend/src/components/ui/Avatar.tsx @@ -54,7 +54,7 @@ export function Avatar({ className={cn( 'inline-flex items-center justify-center rounded-full', 'bg-gradient-to-br', - 'text-bg font-bold select-none flex-shrink-0', + 'text-on-accent font-bold select-none flex-shrink-0', container, text, gradientCls, diff --git a/frontend/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx index 16c2c58..ddcabb6 100644 --- a/frontend/src/components/ui/Button.tsx +++ b/frontend/src/components/ui/Button.tsx @@ -20,7 +20,7 @@ const variantClasses: Record = { 'bg-surface border-border-base text-text-2 ' + 'hover:text-text-base hover:border-border-hi hover:bg-surface-hi', primary: - 'bg-coral border-coral text-bg font-medium ' + + 'bg-coral border-coral text-on-accent font-medium ' + 'hover:bg-coral-2 hover:border-coral-2', ghost: 'bg-transparent border-transparent text-text-2 ' + diff --git a/frontend/src/index.css b/frontend/src/index.css index 63d360e..64e1dfa 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -33,6 +33,7 @@ /* Colors — brand / accent */ --color-coral: #FF6B35; --color-coral-2: #FF8552; + --color-on-accent: #0a0a0b; /* Colors — semantic accents (namespaced to avoid clashing with Tailwind's built-in green-500 / purple-500 / etc. families) */ @@ -72,14 +73,71 @@ --animate-fade: fade 0.3s ease; } +:root { + color-scheme: dark; + --canvas-grid: #26262c; + --control-bg: #171717; + --control-border: #333333; + --control-button-bg: #171717; + --control-button-hover: #262626; + --control-divider: #262626; + --control-text: #a3a3a3; + --control-text-hover: #f5f5f5; + --minimap-mask: rgba(0, 0, 0, 0.3); + --rich-text-strong: #e5e5e5; + --rich-text-code-bg: #262626; +} + +:root[data-theme='light'] { + color-scheme: light; + + --color-bg: #f7f4ef; + --color-panel: #fffaf2; + --color-surface: #f0ebe2; + --color-surface-hi: #e8dfd2; + + --color-border-base: #d9cfc0; + --color-border-hi: #c3b5a3; + + --color-text-base: #1c1713; + --color-text-2: #5f544b; + --color-text-3: #81746a; + --color-text-4: #a19389; + + --color-coral-glow: rgba(255, 107, 53, 0.18); + --color-accent-green-glow: rgba(22, 163, 74, 0.12); + --color-accent-purple-glow: rgba(147, 51, 234, 0.12); + --color-accent-blue-glow: rgba(37, 99, 235, 0.12); + --color-accent-amber-glow: rgba(217, 119, 6, 0.14); + --color-accent-pink-glow: rgba(219, 39, 119, 0.12); + + --shadow-window: 0 40px 80px -24px rgba(63, 43, 24, 0.22), 0 20px 40px -24px rgba(63, 43, 24, 0.18); + --shadow-card-hover: 0 12px 30px -12px rgba(63, 43, 24, 0.18); + --shadow-ai-bar: 0 0 0 3px rgba(244,114,182,0.07), 0 10px 40px -12px rgba(161, 93, 53, 0.18); + --shadow-popup: 0 20px 40px -12px rgba(63, 43, 24, 0.22), 0 4px 16px -6px rgba(63, 43, 24, 0.18); + --shadow-coral-glow: 0 0 40px rgba(255,107,53,0.18); + --shadow-node-selected: 0 0 0 3px rgba(255,107,53,0.18), 0 0 34px rgba(255,107,53,0.18); + + --canvas-grid: #d9cfc0; + --control-bg: #fffaf2; + --control-border: #d9cfc0; + --control-button-bg: #fffaf2; + --control-button-hover: #f0ebe2; + --control-divider: #d9cfc0; + --control-text: #5f544b; + --control-text-hover: #1c1713; + --minimap-mask: rgba(120, 99, 77, 0.18); + --rich-text-strong: #2b211a; + --rich-text-code-bg: #e8dfd2; +} + /* ─── Base / reset ────────────────────────────────────────────────────────── */ html, body, #root { height: 100%; margin: 0; padding: 0; - /* Updated to new token value — one digit change from #0a0a0a → #0a0a0b */ - background-color: #0a0a0b; - color: #f5f5f5; + background-color: var(--color-bg); + color: var(--color-text-base); font-family: var(--font-sans); -webkit-font-smoothing: antialiased; font-feature-settings: "ss01", "ss02", "cv11"; @@ -92,8 +150,8 @@ html, body, #root { * :-internal-is-dark-theme selectors that beat Tailwind utility classes. * Safari ignores these, which is why it looked correct. */ -:root { - color-scheme: dark; +html { + scrollbar-color: var(--color-border-base) transparent; } /* @@ -124,18 +182,18 @@ select { /* ─── Task 003: Global utilities ──────────────────────────────────────────── */ @layer utilities { - /* Page background — coral + pink radial gradients over #0a0a0b */ + /* Page background — coral + pink radial gradients over the themed bg */ .page-bg { background: radial-gradient(900px 500px at 20% -10%, rgba(255, 107, 53, 0.08), transparent 60%), radial-gradient(700px 400px at 90% 10%, rgba(244, 114, 182, 0.05), transparent 60%), - #0a0a0b; + var(--color-bg); } /* Canvas background — 24px dotted grid */ .canvas-bg { - background-color: #0a0a0b; - background-image: radial-gradient(circle, #26262c 1px, transparent 1px); + background-color: var(--color-bg); + background-image: radial-gradient(circle, var(--canvas-grid) 1px, transparent 1px); background-size: 24px 24px; } @@ -165,24 +223,24 @@ select { /* ─── React Flow dark theme overrides (load-bearing — do not remove) ──────── */ .react-flow__controls { - background: #171717 !important; - border: 1px solid #333 !important; + background: var(--control-bg) !important; + border: 1px solid var(--control-border) !important; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4) !important; border-radius: 6px !important; overflow: hidden; } .react-flow__controls-button { - background: #171717 !important; - border-bottom: 1px solid #262626 !important; - color: #a3a3a3 !important; + background: var(--control-button-bg) !important; + border-bottom: 1px solid var(--control-divider) !important; + color: var(--control-text) !important; width: 28px !important; height: 28px !important; } .react-flow__controls-button:hover { - background: #262626 !important; - color: #f5f5f5 !important; + background: var(--control-button-hover) !important; + color: var(--control-text-hover) !important; } .react-flow__controls-button svg { @@ -203,7 +261,7 @@ select { /* Darken the area outside the viewport rect */ .react-flow__minimap-mask { - fill: rgba(0, 0, 0, 0.3); + fill: var(--minimap-mask); } /* Minimap node default fill — overridden per-type via nodeColor prop */ @@ -230,7 +288,7 @@ select { } .node-desc-html strong { font-weight: 600; - color: #e5e5e5; + color: var(--rich-text-strong); } .node-desc-html em { font-style: italic; @@ -238,7 +296,7 @@ select { .node-desc-html code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 0.9em; - background: #262626; + background: var(--rich-text-code-bg); padding: 0 3px; border-radius: 3px; } @@ -248,7 +306,7 @@ select { font-size: inherit; font-weight: 600; margin: 0; - color: #e5e5e5; + color: var(--rich-text-strong); } .node-desc-html ul, .node-desc-html ol { diff --git a/frontend/src/pages/OverviewPage.tsx b/frontend/src/pages/OverviewPage.tsx index 60a9b5e..faa6ee1 100644 --- a/frontend/src/pages/OverviewPage.tsx +++ b/frontend/src/pages/OverviewPage.tsx @@ -330,7 +330,7 @@ export function OverviewPage() { @@ -165,8 +165,8 @@ function ConsentOption({ className="mt-0.5" /> - {label} - — {hint} + {label} + — {hint} ) diff --git a/frontend/src/components/agents-settings/ModelPricingTable.tsx b/frontend/src/components/agents-settings/ModelPricingTable.tsx index f632599..7fbcf48 100644 --- a/frontend/src/components/agents-settings/ModelPricingTable.tsx +++ b/frontend/src/components/agents-settings/ModelPricingTable.tsx @@ -34,11 +34,11 @@ export function ModelPricingTable({ pricing, onChange }: Props) { return (
- + @@ -50,7 +50,7 @@ export function ModelPricingTable({ pricing, onChange }: Props) { @@ -60,9 +60,9 @@ export function ModelPricingTable({ pricing, onChange }: Props) { - ))} {/* Add row */} - +
Model Input ($/1M tokens) Output ($/1M tokens)
No pricing overrides — falling back to LiteLLM defaults.
+ {modelId} @@ -77,7 +77,7 @@ export function ModelPricingTable({ pricing, onChange }: Props) { }) } data-testid={`pricing-${modelId}-input`} - className="w-28 bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-xs outline-none focus:border-neutral-500" + className="w-28 bg-surface border border-border-base rounded px-2 py-1 text-xs text-text-base outline-none focus:border-border-hi" /> @@ -92,7 +92,7 @@ export function ModelPricingTable({ pricing, onChange }: Props) { }) } data-testid={`pricing-${modelId}-output`} - className="w-28 bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-xs outline-none focus:border-neutral-500" + className="w-28 bg-surface border border-border-base rounded px-2 py-1 text-xs text-text-base outline-none focus:border-border-hi" /> @@ -108,7 +108,7 @@ export function ModelPricingTable({ pricing, onChange }: Props) {
setNewId(e.target.value)} placeholder="claude-haiku-3-5" data-testid="pricing-new-id" - className="w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-xs outline-none focus:border-neutral-500" + className="w-full bg-panel border border-border-base rounded px-2 py-1 text-xs text-text-base placeholder:text-text-4 outline-none focus:border-border-hi" /> @@ -127,7 +127,7 @@ export function ModelPricingTable({ pricing, onChange }: Props) { onChange={(e) => setNewInput(e.target.value)} placeholder="0.80" data-testid="pricing-new-input" - className="w-28 bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-xs outline-none focus:border-neutral-500" + className="w-28 bg-panel border border-border-base rounded px-2 py-1 text-xs text-text-base placeholder:text-text-4 outline-none focus:border-border-hi" /> @@ -138,7 +138,7 @@ export function ModelPricingTable({ pricing, onChange }: Props) { onChange={(e) => setNewOutput(e.target.value)} placeholder="4.00" data-testid="pricing-new-output" - className="w-28 bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-xs outline-none focus:border-neutral-500" + className="w-28 bg-panel border border-border-base rounded px-2 py-1 text-xs text-text-base placeholder:text-text-4 outline-none focus:border-border-hi" /> @@ -147,7 +147,7 @@ export function ModelPricingTable({ pricing, onChange }: Props) { onClick={addRow} disabled={!newId.trim()} data-testid="pricing-add" - className="text-xs text-blue-400 hover:text-blue-300 disabled:opacity-40 disabled:cursor-not-allowed" + className="text-xs text-coral hover:text-coral-2 disabled:opacity-40 disabled:cursor-not-allowed" > + Add row diff --git a/frontend/src/components/agents-settings/PerAgentOverrideTable.tsx b/frontend/src/components/agents-settings/PerAgentOverrideTable.tsx index b1adb27..ada8c28 100644 --- a/frontend/src/components/agents-settings/PerAgentOverrideTable.tsx +++ b/frontend/src/components/agents-settings/PerAgentOverrideTable.tsx @@ -32,11 +32,11 @@ export function PerAgentOverrideTable({ agents, defaultModel, onChange }: Props) return (
- + @@ -51,9 +51,9 @@ export function PerAgentOverrideTable({ agents, defaultModel, onChange }: Props) -
Agent Model Turn limit
+ {agentId} @@ -69,7 +69,7 @@ export function PerAgentOverrideTable({ agents, defaultModel, onChange }: Props) ) } data-testid={`agent-${agentId}-model`} - className="w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-xs outline-none focus:border-neutral-500" + className="w-full bg-surface border border-border-base rounded px-2 py-1 text-xs text-text-base placeholder:text-text-4 outline-none focus:border-border-hi" /> @@ -86,7 +86,7 @@ export function PerAgentOverrideTable({ agents, defaultModel, onChange }: Props) ) } data-testid={`agent-${agentId}-turn_limit`} - className="w-20 bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-xs outline-none focus:border-neutral-500" + className="w-20 bg-surface border border-border-base rounded px-2 py-1 text-xs text-text-base placeholder:text-text-4 outline-none focus:border-border-hi" /> @@ -103,7 +103,7 @@ export function PerAgentOverrideTable({ agents, defaultModel, onChange }: Props) ) } data-testid={`agent-${agentId}-budget_usd`} - className="w-24 bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-xs outline-none focus:border-neutral-500" + className="w-24 bg-surface border border-border-base rounded px-2 py-1 text-xs text-text-base placeholder:text-text-4 outline-none focus:border-border-hi" /> @@ -117,7 +117,7 @@ export function PerAgentOverrideTable({ agents, defaultModel, onChange }: Props) ) } data-testid={`agent-${agentId}-budget_scope`} - className="bg-neutral-800 border border-neutral-700 rounded px-2 py-1 text-xs outline-none focus:border-neutral-500" + className="bg-surface border border-border-base rounded px-2 py-1 text-xs text-text-base outline-none focus:border-border-hi" > diff --git a/frontend/src/components/common/Modal.tsx b/frontend/src/components/common/Modal.tsx index b721876..59becfe 100644 --- a/frontend/src/components/common/Modal.tsx +++ b/frontend/src/components/common/Modal.tsx @@ -43,11 +43,11 @@ export function Modal({ open, onClose, title, children, footer, width = 440 }: M onClick={(e) => e.stopPropagation()} style={{ width, - background: '#171717', - border: '1px solid #333', + background: 'var(--color-panel)', + border: '1px solid var(--color-border-base)', borderRadius: 10, - boxShadow: '0 16px 48px rgba(0,0,0,0.6)', - color: '#e5e5e5', + boxShadow: 'var(--shadow-popup)', + color: 'var(--color-text-base)', display: 'flex', flexDirection: 'column', maxHeight: '85vh', @@ -56,7 +56,7 @@ export function Modal({ open, onClose, title, children, footer, width = 440 }: M
diff --git a/frontend/src/components/ui/Button.tsx b/frontend/src/components/ui/Button.tsx index ddcabb6..92e7827 100644 --- a/frontend/src/components/ui/Button.tsx +++ b/frontend/src/components/ui/Button.tsx @@ -21,7 +21,8 @@ const variantClasses: Record = { 'hover:text-text-base hover:border-border-hi hover:bg-surface-hi', primary: 'bg-coral border-coral text-on-accent font-medium ' + - 'hover:bg-coral-2 hover:border-coral-2', + 'hover:bg-coral-2 hover:border-coral-2 ' + + 'disabled:opacity-100 disabled:bg-surface-hi disabled:border-border-base disabled:text-text-3', ghost: 'bg-transparent border-transparent text-text-2 ' + 'hover:bg-surface hover:border-border-base', diff --git a/frontend/src/index.css b/frontend/src/index.css index 64e1dfa..de1dfb7 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -653,3 +653,83 @@ select { transition: all 0.2s ease; color: var(--color-text-2); } + +/* ─── Light-theme compatibility for legacy dark utility classes ───────────── + * Several older app pages were built directly with neutral-800/900 Tailwind + * utilities. In light mode those utilities leave black cards, tables and form + * controls on an otherwise light canvas. Keep dark mode untouched, but remap + * the common legacy neutral ramp to semantic theme tokens when light is active. + */ +:root[data-theme='light'] .bg-neutral-950, +:root[data-theme='light'] .bg-neutral-900, +:root[data-theme='light'] .bg-neutral-800 { + background-color: var(--color-panel) !important; +} + +:root[data-theme='light'] .bg-neutral-800\/40, +:root[data-theme='light'] .bg-neutral-800\/50, +:root[data-theme='light'] .bg-neutral-900\/10 { + background-color: color-mix(in srgb, var(--color-surface) 65%, transparent) !important; +} + +:root[data-theme='light'] .bg-neutral-700 { + background-color: var(--color-surface-hi) !important; +} + +:root[data-theme='light'] .hover\:bg-neutral-800:hover, +:root[data-theme='light'] .hover\:bg-neutral-800\/40:hover, +:root[data-theme='light'] .hover\:bg-neutral-800\/50:hover, +:root[data-theme='light'] .hover\:bg-neutral-700:hover, +:root[data-theme='light'] .hover\:bg-neutral-600:hover { + background-color: var(--color-surface-hi) !important; +} + +:root[data-theme='light'] .border-neutral-900, +:root[data-theme='light'] .border-neutral-800, +:root[data-theme='light'] .border-neutral-700, +:root[data-theme='light'] .border-neutral-600, +:root[data-theme='light'] .border-neutral-500 { + border-color: var(--color-border-base) !important; +} + +:root[data-theme='light'] .hover\:border-neutral-700:hover, +:root[data-theme='light'] .hover\:border-neutral-600:hover, +:root[data-theme='light'] .hover\:border-neutral-500:hover, +:root[data-theme='light'] .focus\:border-neutral-600:focus, +:root[data-theme='light'] .focus\:border-neutral-500:focus { + border-color: var(--color-border-hi) !important; +} + +:root[data-theme='light'] .text-neutral-100, +:root[data-theme='light'] .text-neutral-200, +:root[data-theme='light'] .text-neutral-300 { + color: var(--color-text-base) !important; +} + +:root[data-theme='light'] .text-neutral-400, +:root[data-theme='light'] .text-neutral-500 { + color: var(--color-text-2) !important; +} + +:root[data-theme='light'] .text-neutral-600, +:root[data-theme='light'] .text-neutral-700 { + color: var(--color-text-3) !important; +} + +:root[data-theme='light'] .hover\:text-neutral-100:hover, +:root[data-theme='light'] .hover\:text-neutral-200:hover, +:root[data-theme='light'] .hover\:text-neutral-300:hover, +:root[data-theme='light'] .hover\:text-white:hover { + color: var(--color-text-base) !important; +} + +:root[data-theme='light'] .bg-coral.text-white, +:root[data-theme='light'] .bg-neutral-700.text-white, +:root[data-theme='light'] .bg-neutral-800.text-white, +:root[data-theme='light'] .bg-neutral-900.text-white { + color: var(--color-text-base) !important; +} + +:root[data-theme='light'] .ring-offset-\[\#171717\] { + --tw-ring-offset-color: var(--color-panel) !important; +} diff --git a/frontend/src/pages/AgentsSettingsPage.tsx b/frontend/src/pages/AgentsSettingsPage.tsx index ce7c398..1735040 100644 --- a/frontend/src/pages/AgentsSettingsPage.tsx +++ b/frontend/src/pages/AgentsSettingsPage.tsx @@ -248,7 +248,7 @@ export function AgentsSettingsPage() {
You need admin permissions to view agent settings.
@@ -267,7 +267,7 @@ export function AgentsSettingsPage() {
Loading…
@@ -396,7 +396,7 @@ export function AgentsSettingsPage() {

Agent settings

-

+

Configure your workspace's AI agents — pick an LLM provider, plug in your API key, set privacy preferences, and tune per-agent overrides. Changes apply to all members of this workspace. @@ -433,7 +433,7 @@ export function AgentsSettingsPage() { placeholder="https://my-proxy.example.com/v1" className={inputCls} /> -

+

Must speak the OpenAI Chat Completions protocol.

@@ -443,7 +443,7 @@ export function AgentsSettingsPage() { data-testid="llm-base-url" value={draft.litellm.base_url} readOnly - className={`${inputCls} text-neutral-400 cursor-not-allowed`} + className={`${inputCls} text-text-3 cursor-not-allowed`} /> )} @@ -478,7 +478,7 @@ export function AgentsSettingsPage() {
-

+

{original.litellm.has_key ? 'A key is already saved. Type a new value to replace it.' : 'No key saved yet — agents will fall back to the bundled key (if any).'} @@ -566,11 +566,11 @@ export function AgentsSettingsPage() { title="Privacy / Analytics" hint="Controls whether agent traces are sent to the self-hosted Langfuse instance." > -

+

Current mode:{' '} {original.analytics_consent} @@ -650,7 +650,7 @@ export function AgentsSettingsPage() { onClick={onDiscard} disabled={!dirty || update.isPending} data-testid="discard-btn" - className="text-xs text-neutral-400 hover:text-neutral-200 px-3 py-1.5 disabled:opacity-40" + className="text-xs text-text-2 hover:text-text-base px-3 py-1.5 disabled:opacity-40" > Discard @@ -659,7 +659,7 @@ export function AgentsSettingsPage() { onClick={onSave} disabled={!dirty || update.isPending} data-testid="save-btn" - className="bg-blue-600 hover:bg-blue-500 text-white text-xs font-medium rounded px-4 py-1.5 disabled:opacity-40" + className="bg-coral hover:bg-coral-2 text-on-accent text-xs font-medium rounded px-4 py-1.5 disabled:bg-surface-hi disabled:text-text-3 disabled:border disabled:border-border-base disabled:opacity-100 disabled:cursor-not-allowed" > {update.isPending ? 'Saving…' : 'Save'} @@ -713,7 +713,7 @@ const EDITS_POLICY_OPTIONS: { // ─── Layout primitives ────────────────────────────────────────────────────── const inputCls = - 'w-full bg-neutral-800 border border-neutral-700 rounded px-2 py-1.5 text-sm outline-none focus:border-neutral-500' + 'w-full bg-surface border border-border-base rounded px-2 py-1.5 text-sm text-text-base placeholder:text-text-4 outline-none focus:border-border-hi' function Section({ title, @@ -727,7 +727,7 @@ function Section({ return (

{title}

- {hint &&

{hint}

} + {hint &&

{hint}

}
{children}
) @@ -742,7 +742,7 @@ function Field({ }) { return (
- + {children}
) @@ -769,8 +769,8 @@ function CardRadio({ ) diff --git a/frontend/src/pages/ConnectionsPage.tsx b/frontend/src/pages/ConnectionsPage.tsx index 4e2c96a..f80a1f2 100644 --- a/frontend/src/pages/ConnectionsPage.tsx +++ b/frontend/src/pages/ConnectionsPage.tsx @@ -56,7 +56,7 @@ export function ConnectionsPage() { value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Search connections…" - className="w-72 bg-neutral-900 border border-neutral-800 rounded px-3 py-1.5 text-sm outline-none focus:border-neutral-600" + className="w-72 bg-surface border border-border-base rounded px-3 py-1.5 text-sm text-text-base placeholder:text-text-4 outline-none focus:border-border-hi" />
@@ -67,10 +67,10 @@ export function ConnectionsPage() {
)} -
+
- + @@ -80,23 +80,23 @@ export function ConnectionsPage() { {filtered.map((r) => ( - - - - + + + - +
Source Target Direction
{r.source}{r.target} +
{r.source}{r.target} {r.direction === 'bidirectional' ? '⇄ bidirectional' : '→ outgoing'} {r.label || '—'}{r.label || '—'} {r.protocols.length === 0 ? ( - + ) : (
{r.protocols.slice(0, 4).map((p) => ( ))} {r.protocols.length > 4 && ( - + +{r.protocols.length - 4} )} diff --git a/frontend/src/pages/ObjectsPage.tsx b/frontend/src/pages/ObjectsPage.tsx index ea49f26..cf4e1b6 100644 --- a/frontend/src/pages/ObjectsPage.tsx +++ b/frontend/src/pages/ObjectsPage.tsx @@ -60,7 +60,7 @@ export function ObjectsPage() { value={search} onChange={(e) => setSearch(e.target.value)} placeholder="Search objects…" - className="w-72 bg-neutral-900 border border-neutral-800 rounded px-3 py-1.5 text-sm outline-none focus:border-neutral-600" + className="w-72 bg-surface border border-border-base rounded px-3 py-1.5 text-sm text-text-base placeholder:text-text-4 outline-none focus:border-border-hi" />
@@ -71,10 +71,10 @@ export function ObjectsPage() { )} -
+
- + @@ -124,12 +124,12 @@ function ObjectRow({ .filter((t): t is Technology => Boolean(t)) return ( - + - + - +
Name Type Status
{TYPE_ICONS[obj.type]} - {obj.name} + {obj.name} {TYPE_LABELS[obj.type]}{TYPE_LABELS[obj.type]} {technologies.length === 0 ? ( - + ) : (
{technologies.slice(0, 4).map((t) => ( ))} {technologies.length > 4 && ( - +{technologies.length - 4} + +{technologies.length - 4} )}
)}
{obj.owner_team || '—'}{obj.owner_team || '—'} {diagrams.length === 0 ? ( - + ) : (
{diagrams.slice(0, 2).map((d) => ( @@ -172,7 +172,7 @@ function ObjectRow({ ))} {diagrams.length > 2 && ( - +{diagrams.length - 2} + +{diagrams.length - 2} )}
)} @@ -183,7 +183,7 @@ function ObjectRow({ e.stopPropagation() onEdit(obj.id) }} - className="px-2 py-1 text-neutral-500 hover:text-neutral-200 hover:bg-neutral-800 rounded text-base leading-none" + className="px-2 py-1 text-text-3 hover:text-text-base hover:bg-surface-hi rounded text-base leading-none" title="Edit object" > ⋯ diff --git a/frontend/src/pages/docs/DocsLayout.tsx b/frontend/src/pages/docs/DocsLayout.tsx index a6eb4cc..e0b6665 100644 --- a/frontend/src/pages/docs/DocsLayout.tsx +++ b/frontend/src/pages/docs/DocsLayout.tsx @@ -57,7 +57,7 @@ export function DocsLayout({ }, [activeId]) return ( -
+
{/* Decorative gradient blob — clipped by its own container so the page wrapper doesn't need overflow:hidden, which can interfere with anchor-jump scroll on some mobile browsers. */} @@ -68,24 +68,24 @@ export function DocsLayout({ />
-
+
- ArchFlow + ArchFlow
GitHub ← Back home @@ -99,7 +99,7 @@ export function DocsLayout({ lg+ where the sidebar takes over. */}