From 26e5514a8e638e7aa0844a8e188948aed35c19d1 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Fri, 19 Jun 2026 10:34:12 -0700 Subject: [PATCH] FN-6724: add Factory Mono theme Add a monochrome Factory theme with red accents and no glow effects. - Register Factory Mono across theme validation, typing, selector UI, and startup hydration. - Define dark and light Factory Mono CSS tokens, swatches, red CTA accents, uppercase controls, and glow-free active states. - Cover Factory Mono token behavior with a theme hook regression test and document the expanded theme count. - Add a minor changeset for the published Fusion package. Files changed: .changeset/factory-mono-theme.md | 5 + docs/dashboard-guide.md | 2 +- packages/core/src/types.ts | 1 + .../dashboard/app/components/ThemeSelector.css | 14 ++ .../dashboard/app/components/ThemeSelector.tsx | 1 + .../dashboard/app/hooks/__tests__/useTheme.test.ts | 28 ++++ packages/dashboard/app/index.html | 2 +- packages/dashboard/app/public/theme-data.css | 177 +++++++++++++++++++++ 8 files changed, 228 insertions(+), 2 deletions(-) Fusion-Task-Id: FN-6724 Fusion-Task-Lineage: 861fbcef-d1b1-4bfb-bd60-0196ce825771 --- .changeset/factory-mono-theme.md | 5 + docs/dashboard-guide.md | 2 +- packages/core/src/types.ts | 1 + .../app/components/ThemeSelector.css | 14 ++ .../app/components/ThemeSelector.tsx | 1 + .../app/hooks/__tests__/useTheme.test.ts | 28 +++ packages/dashboard/app/index.html | 2 +- packages/dashboard/app/public/theme-data.css | 177 ++++++++++++++++++ 8 files changed, 228 insertions(+), 2 deletions(-) create mode 100644 .changeset/factory-mono-theme.md diff --git a/.changeset/factory-mono-theme.md b/.changeset/factory-mono-theme.md new file mode 100644 index 0000000000..4d1b1930cc --- /dev/null +++ b/.changeset/factory-mono-theme.md @@ -0,0 +1,5 @@ +--- +"@runfusion/fusion": minor +--- + +Add the `factory-mono` dashboard color theme, a monochrome Factory variant with red accents and neutralized glow effects. diff --git a/docs/dashboard-guide.md b/docs/dashboard-guide.md index 5d7dee952c..8c0816c792 100644 --- a/docs/dashboard-guide.md +++ b/docs/dashboard-guide.md @@ -1262,7 +1262,7 @@ Non-Command-Center dashboard CSS uses `--text` as the canonical primary text tok ### Theme system -Dark/light modes via `data-theme`; 54 color themes via `data-color-theme` (lazy-loaded from `app/public/theme-data.css`). +Dark/light modes via `data-theme`; 55 color themes via `data-color-theme` (lazy-loaded from `app/public/theme-data.css`). - **Base tokens** (`--bg`, `--surface`, etc.) — redefine in `:root`, `[data-theme="light"]`, and every theme block. - **Semantic tokens** (`--autopilot-pulse`, `--event-error-text`, `--badge-mission-*`, `--fab-*`) — `:root` + `[data-theme="light"]` only; no per-color-theme overrides. diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts index 39c31184cc..d12995bb4c 100644 --- a/packages/core/src/types.ts +++ b/packages/core/src/types.ts @@ -281,6 +281,7 @@ export const COLOR_THEMES = [ "silver", "solarized", "factory", + "factory-mono", "ayu", "one-dark", "nord", diff --git a/packages/dashboard/app/components/ThemeSelector.css b/packages/dashboard/app/components/ThemeSelector.css index 1605c44803..5a358b7a4e 100644 --- a/packages/dashboard/app/components/ThemeSelector.css +++ b/packages/dashboard/app/components/ThemeSelector.css @@ -367,6 +367,13 @@ --swatch-sample-4: #06b6d4; } +.theme-swatch-factory-mono { + --swatch-sample-1: #0a0a0a; + --swatch-sample-2: #1a1a1a; + --swatch-sample-3: #ef4444; + --swatch-sample-4: #ffffff; +} + .theme-swatch-ayu { --swatch-sample-1: #0f1419; --swatch-sample-2: #131d27; @@ -571,6 +578,13 @@ --swatch-sample-4: #0891b2; } +[data-theme="light"] .theme-swatch-factory-mono { + --swatch-sample-1: #f5f5f5; + --swatch-sample-2: #ffffff; + --swatch-sample-3: #dc2626; + --swatch-sample-4: #1a1a1a; +} + [data-theme="light"] .theme-swatch-ayu { --swatch-sample-1: #fafafa; --swatch-sample-2: #f3f3f3; diff --git a/packages/dashboard/app/components/ThemeSelector.tsx b/packages/dashboard/app/components/ThemeSelector.tsx index b94789f54c..59fbf14d24 100644 --- a/packages/dashboard/app/components/ThemeSelector.tsx +++ b/packages/dashboard/app/components/ThemeSelector.tsx @@ -42,6 +42,7 @@ const COLOR_THEMES: { value: ColorTheme; label: string; className: string }[] = { value: "silver", label: "Silver", className: "theme-swatch-silver" }, { value: "solarized", label: "Solarized", className: "theme-swatch-solarized" }, { value: "factory", label: "Factory", className: "theme-swatch-factory" }, + { value: "factory-mono", label: "Factory Mono", className: "theme-swatch-factory-mono" }, { value: "ayu", label: "Ayu", className: "theme-swatch-ayu" }, { value: "one-dark", label: "One Dark", className: "theme-swatch-one-dark" }, { value: "nord", label: "Nord", className: "theme-swatch-nord" }, diff --git a/packages/dashboard/app/hooks/__tests__/useTheme.test.ts b/packages/dashboard/app/hooks/__tests__/useTheme.test.ts index dcf485d19e..ad389e2a24 100644 --- a/packages/dashboard/app/hooks/__tests__/useTheme.test.ts +++ b/packages/dashboard/app/hooks/__tests__/useTheme.test.ts @@ -491,6 +491,34 @@ describe("useTheme", () => { document.head.removeChild(style); }); + it("applies factory-mono design tokens without glow effects", () => { + localStorageMock[COLOR_THEME_STORAGE_KEY] = "factory-mono"; + + renderHook(() => useTheme()); + + const style = document.createElement("style"); + const baseCss = readFileSync(resolve(PACKAGE_ROOT, "app/styles.css"), "utf8"); + const themeDataCss = readFileSync(resolve(PACKAGE_ROOT, "app/public/theme-data.css"), "utf8"); + style.textContent = baseCss + "\n" + themeDataCss; + document.head.appendChild(style); + document.documentElement.setAttribute("data-color-theme", "factory-mono"); + + expect(document.documentElement.getAttribute("data-color-theme")).toBe("factory-mono"); + + const factoryMonoBlock = themeDataCss.match(/\[data-color-theme="factory-mono"\] \{(?[\s\S]*?)\n\}/)?.groups?.body; + expect(factoryMonoBlock).toBeDefined(); + expect(factoryMonoBlock).toContain("--shadow-glow: none;"); + expect(factoryMonoBlock).toContain("--glow-success: none;"); + expect(factoryMonoBlock).toContain("--glow-warning: none;"); + expect(factoryMonoBlock).toContain("--glow-danger: none;"); + expect(factoryMonoBlock).toContain("--cta-glow: none;"); + expect(factoryMonoBlock).not.toMatch(/--(?:shadow-glow|glow-success|glow-warning|glow-danger|cta-glow):\s*0 0/); + expect(factoryMonoBlock).toContain("--accent: #ef4444;"); + expect(factoryMonoBlock).toContain("--cta-border: #ef4444;"); + + document.head.removeChild(style); + }); + it("supports all valid theme modes", () => { const { result } = renderHook(() => useTheme()); diff --git a/packages/dashboard/app/index.html b/packages/dashboard/app/index.html index 5664b097cf..e2e3e8a30f 100644 --- a/packages/dashboard/app/index.html +++ b/packages/dashboard/app/index.html @@ -160,7 +160,7 @@ try { var mode = localStorage.getItem('kb-dashboard-theme-mode') || 'dark'; var colorTheme = localStorage.getItem('kb-dashboard-color-theme') || 'default'; - var validThemes = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'graphite', 'silver', 'solarized', 'factory', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia']; + var validThemes = ['default', 'ocean', 'forest', 'sunset', 'zen', 'berry', 'high-contrast', 'industrial', 'monochrome', 'slate', 'ash', 'graphite', 'silver', 'solarized', 'factory', 'factory-mono', 'ayu', 'one-dark', 'nord', 'dracula', 'gruvbox', 'tokyo-night', 'catppuccin-mocha', 'github-dark', 'everforest', 'rose-pine', 'kanagawa', 'night-owl', 'palenight', 'monokai-pro', 'slime', 'brutalist', 'neon-city', 'parchment', 'terminal', 'glass', 'horizon', 'vitesse', 'outrun', 'snazzy', 'porple', 'espresso', 'mars', 'poimandres', 'ember', 'rust', 'copper', 'foundry', 'carbon', 'sandstone', 'lagoon', 'frost', 'lavender', 'neon-bloom', 'sepia']; if (!validThemes.includes(colorTheme)) { colorTheme = 'default'; } diff --git a/packages/dashboard/app/public/theme-data.css b/packages/dashboard/app/public/theme-data.css index 4faa911db0..076d5fcbc3 100644 --- a/packages/dashboard/app/public/theme-data.css +++ b/packages/dashboard/app/public/theme-data.css @@ -950,6 +950,183 @@ font-weight: 700; } +/* +FNXC:Theming 2026-06-19-00:00: +Factory Mono is a monochrome Factory variant for FN-6724: keep the industrial monospace density, uppercase controls, and 2px border system while limiting color to red accents and removing pulsing glow effects. +*/ +[data-color-theme="factory-mono"] { + --bg: #0a0a0a; + --surface: #111111; + --card: #1a1a1a; + --card-hover: #222222; + --surface-hover: color-mix(in srgb, var(--surface) 90%, var(--text) 10%); + --border: #333333; + + --text: #f5f5f5; + --text-muted: #a3a3a3; + --text-dim: #737373; + + --todo: #d4d4d4; + --in-progress: #a3a3a3; + --in-progress-rgb: 163, 163, 163; + --in-review: #e5e5e5; + --triage: #737373; + --done: #525252; + + --color-success: #e5e5e5; + --color-warning: #d4d4d4; + --color-error: #ef4444; + --color-muted: #737373; + + --font-primary: "SF Mono", "JetBrains Mono", "Fira Code", Monaco, Consolas, monospace; + --font-mono: "SF Mono", "JetBrains Mono", "Fira Code", Monaco, Consolas, monospace; + + --space-xs: 2px; + --space-sm: 4px; + --space-md: 8px; + --space-lg: 12px; + --space-xl: 16px; + --space-2xl: 24px; + + --radius-sm: 2px; + --radius-md: 4px; + --radius-lg: 6px; + --radius-xl: 8px; + --radius: var(--radius-md); + + --btn-padding: 6px 12px; + --btn-border-width: 2px; + --card-padding: 8px 10px; + --modal-padding: var(--space-md) var(--space-lg); + --header-padding: var(--space-sm) var(--space-lg); + --column-gap: var(--space-sm); + --board-padding: var(--space-md) var(--space-lg); + + --shadow-sm: none; + --shadow-md: 0 1px 0 color-mix(in srgb, #000000 60%, transparent); + --shadow-lg: 0 1px 0 color-mix(in srgb, #000000 70%, transparent); + --shadow-glow: none; + --glow-success: none; + --glow-warning: none; + --glow-danger: none; + --focus-ring: 0 0 0 var(--btn-border-width) color-mix(in srgb, var(--accent) 30%, transparent); + --focus-ring-strong: 0 0 0 var(--btn-border-width) color-mix(in srgb, var(--accent) 45%, transparent); + --shadow: var(--shadow-lg); + + --transition-instant: 0.05s ease; + --transition-fast: 0.1s ease; + --transition-normal: 0.15s ease; + --transition-slow: 0.2s ease; + + --cta-bg: transparent; + --cta-border: #ef4444; + --cta-text: #ef4444; + --cta-bg-hover: #ef4444; + --cta-border-hover: #f87171; + --cta-glow: none; + --logo-accent: #ef4444; + --color-info: #d4d4d4; + --accent: #ef4444; + --accent-text: #0a0a0a; +} + +[data-color-theme="factory-mono"][data-theme="light"] { + --bg: #f5f5f5; + --surface: #ffffff; + --card: #fafafa; + --card-hover: #f0f0f0; + --surface-hover: color-mix(in srgb, var(--surface) 92%, var(--text) 8%); + --border: #d4d4d4; + + --text: #1a1a1a; + --text-muted: #525252; + --text-dim: #737373; + + --todo: #525252; + --in-progress: #737373; + --in-progress-rgb: 115, 115, 115; + --in-review: #404040; + --triage: #a3a3a3; + --done: #8a8a8a; + + --color-success: #404040; + --color-warning: #525252; + --color-error: #dc2626; + --color-muted: #737373; + + --shadow-sm: none; + --shadow-md: 0 1px 0 color-mix(in srgb, var(--text) 12%, transparent); + --shadow-lg: 0 1px 0 color-mix(in srgb, var(--text) 18%, transparent); + --shadow-glow: none; + --glow-success: none; + --glow-warning: none; + --glow-danger: none; + --focus-ring: 0 0 0 var(--btn-border-width) color-mix(in srgb, var(--accent) 25%, transparent); + --focus-ring-strong: 0 0 0 var(--btn-border-width) color-mix(in srgb, var(--accent) 38%, transparent); + --shadow: var(--shadow-lg); + + --cta-bg: transparent; + --cta-border: #dc2626; + --cta-text: #dc2626; + --cta-bg-hover: #dc2626; + --cta-border-hover: #ef4444; + --cta-glow: none; + --logo-accent: #dc2626; + --color-info: #525252; + --accent: #dc2626; + --accent-text: #ffffff; +} + +[data-color-theme="factory-mono"] .card.agent-active { + border-color: var(--accent); + box-shadow: none; + animation: none; +} + +[data-color-theme="factory-mono"][data-theme="light"] .card.agent-active { + border-color: var(--accent); + box-shadow: none; + animation: none; +} + +[data-color-theme="factory-mono"] .btn { + text-transform: uppercase; + letter-spacing: 0.05em; + font-weight: 600; +} + +[data-color-theme="factory-mono"] .btn-primary, +[data-color-theme="factory-mono"] .btn-task-create { + background: transparent; + border-color: var(--accent); + color: var(--accent); + box-shadow: none; +} + +[data-color-theme="factory-mono"] .btn-primary:hover, +[data-color-theme="factory-mono"] .btn-task-create:hover { + background: var(--accent); + color: var(--accent-text); + box-shadow: none; +} + +[data-color-theme="factory-mono"] .card, +[data-color-theme="factory-mono"] .column { + border-width: var(--btn-border-width); +} + +[data-color-theme="factory-mono"] .column-header h2 { + text-transform: uppercase; + letter-spacing: 0.1em; + font-size: 0.75rem; +} + +[data-color-theme="factory-mono"] .column-count, +[data-color-theme="factory-mono"] .card-id { + font-family: var(--font-mono); + font-weight: 700; +} + /* AYU - Popular code editor theme with dark and light variants */ [data-color-theme="ayu"] { --bg: #0f1419;