Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { cn } from "@superset/ui/utils";
import { HiCheck } from "react-icons/hi2";
import type { Theme } from "shared/themes";
import { getTerminalColors, type Theme } from "shared/themes";

interface ThemeCardProps {
theme: Theme;
Expand All @@ -9,15 +9,16 @@ interface ThemeCardProps {
}

export function ThemeCard({ theme, isSelected, onSelect }: ThemeCardProps) {
const bgColor = theme.terminal.background;
const fgColor = theme.terminal.foreground;
const terminal = getTerminalColors(theme);
const bgColor = terminal.background;
const fgColor = terminal.foreground;
const accentColors = [
theme.terminal.red,
theme.terminal.green,
theme.terminal.yellow,
theme.terminal.blue,
theme.terminal.magenta,
theme.terminal.cyan,
terminal.red,
terminal.green,
terminal.yellow,
terminal.blue,
terminal.magenta,
terminal.cyan,
];

return (
Expand All @@ -41,7 +42,7 @@ export function ThemeCard({ theme, isSelected, onSelect }: ThemeCardProps) {
<div className="flex items-center gap-1">
<span
className="text-[11px] font-mono"
style={{ color: theme.terminal.green }}
style={{ color: terminal.green }}
>
$
</span>
Expand All @@ -51,13 +52,13 @@ export function ThemeCard({ theme, isSelected, onSelect }: ThemeCardProps) {
</div>
<div
className="text-[11px] font-mono"
style={{ color: theme.terminal.cyan }}
style={{ color: terminal.cyan }}
>
Starting development server...
</div>
<div
className="text-[11px] font-mono"
style={{ color: theme.terminal.yellow }}
style={{ color: terminal.yellow }}
>
Ready on http://localhost:3000
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,11 @@ import { debounce } from "lodash";
import { trpcClient } from "renderer/lib/trpc-client";
import { toXtermTheme } from "renderer/stores/theme/utils";
import { isAppHotkey } from "shared/hotkeys";
import { builtInThemes, DEFAULT_THEME_ID } from "shared/themes";
import {
builtInThemes,
DEFAULT_THEME_ID,
getTerminalColors,
} from "shared/themes";
import { RESIZE_DEBOUNCE_MS, TERMINAL_OPTIONS } from "./config";
import { FilePathLinkProvider, UrlLinkProvider } from "./link-providers";
import { suppressQueryResponses } from "./suppressQueryResponses";
Expand All @@ -31,15 +35,15 @@ export function getDefaultTerminalTheme(): ITheme {
const themeId = localStorage.getItem("theme-id") ?? DEFAULT_THEME_ID;
const theme = builtInThemes.find((t) => t.id === themeId);
if (theme) {
return toXtermTheme(theme.terminal);
return toXtermTheme(getTerminalColors(theme));
}
} catch {
// Fall through to default
}
// Final fallback to default theme
const defaultTheme = builtInThemes.find((t) => t.id === DEFAULT_THEME_ID);
return defaultTheme
? toXtermTheme(defaultTheme.terminal)
? toXtermTheme(getTerminalColors(defaultTheme))
: { background: "#1a1a1a", foreground: "#d4d4d4" };
}

Expand Down
8 changes: 6 additions & 2 deletions apps/desktop/src/renderer/stores/theme/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { ITheme } from "@xterm/xterm";
import {
builtInThemes,
DEFAULT_THEME_ID,
getTerminalColors,
type Theme,
type ThemeMetadata,
} from "shared/themes";
Expand Down Expand Up @@ -67,7 +68,10 @@ function syncThemeToLocalStorage(theme: Theme): void {
try {
localStorage.setItem("theme-type", theme.type);
localStorage.setItem("theme-id", theme.id);
localStorage.setItem("theme-terminal", JSON.stringify(theme.terminal));
localStorage.setItem(
"theme-terminal",
JSON.stringify(getTerminalColors(theme)),
);
} catch {
// localStorage may not be available
}
Expand All @@ -90,7 +94,7 @@ function applyTheme(theme: Theme): {

// Convert to editor-specific formats
return {
terminalTheme: toXtermTheme(theme.terminal),
terminalTheme: toXtermTheme(getTerminalColors(theme)),
monacoTheme: toMonacoTheme(theme),
};
}
Expand Down
7 changes: 4 additions & 3 deletions apps/desktop/src/renderer/stores/theme/utils/monaco-theme.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { editor } from "monaco-editor";
import type { Theme } from "shared/themes/types";
import { getTerminalColors, type Theme } from "shared/themes";
import { toHexAuto, withAlpha } from "shared/themes/utils";

export interface MonacoTheme {
Expand All @@ -10,7 +10,8 @@ export interface MonacoTheme {
}

function createEditorColors(theme: Theme): editor.IColors {
const { terminal, ui } = theme;
const terminal = getTerminalColors(theme);
const { ui } = theme;
const hex = toHexAuto;
const alpha = withAlpha;

Expand Down Expand Up @@ -62,7 +63,7 @@ function createEditorColors(theme: Theme): editor.IColors {
}

function createTokenRules(theme: Theme): editor.ITokenThemeRule[] {
const { terminal } = theme;
const terminal = getTerminalColors(theme);
const hex = (color: string) => toHexAuto(color).slice(1);

return [
Expand Down
42 changes: 21 additions & 21 deletions apps/desktop/src/shared/themes/built-in/light.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,29 +49,29 @@ export const lightTheme: Theme = {

terminal: {
background: "#ffffff",
foreground: "#333333",
cursor: "#333333",
foreground: "#000000",
cursor: "#000000",
cursorAccent: "#ffffff",
selectionBackground: "rgba(0, 0, 0, 0.15)",
selectionBackground: "#add6ff",

// Standard ANSI colors
black: "#000000",
red: "#c91b00",
green: "#00c200",
yellow: "#c7c400",
blue: "#0225c7",
magenta: "#c930c7",
cyan: "#00c5c7",
white: "#c7c7c7",
// Standard ANSI colors (xterm defaults)
black: "#2e3436",
red: "#cc0000",
green: "#4e9a06",
yellow: "#c4a000",
blue: "#3465a4",
magenta: "#75507b",
cyan: "#06989a",
white: "#d3d7cf",

// Bright ANSI colors
brightBlack: "#686868",
brightRed: "#ff6e6e",
brightGreen: "#5ffa68",
brightYellow: "#fffc67",
brightBlue: "#6871ff",
brightMagenta: "#ff77ff",
brightCyan: "#5ffdff",
brightWhite: "#ffffff",
// Bright ANSI colors (xterm defaults)
brightBlack: "#555753",
brightRed: "#ef2929",
brightGreen: "#8ae234",
brightYellow: "#fce94f",
brightBlue: "#729fcf",
brightMagenta: "#ad7fa8",
brightCyan: "#34e2e2",
brightWhite: "#eeeeec",
},
};
6 changes: 6 additions & 0 deletions apps/desktop/src/shared/themes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,9 @@ export {
oneDarkTheme,
} from "./built-in";
export type { TerminalColors, Theme, ThemeMetadata, UIColors } from "./types";
export {
DEFAULT_TERMINAL_COLORS_DARK,
DEFAULT_TERMINAL_COLORS_LIGHT,
getDefaultTerminalColors,
getTerminalColors,
} from "./types";
84 changes: 82 additions & 2 deletions apps/desktop/src/shared/themes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,86 @@
* Themes control both UI colors (via CSS variables) and terminal colors (via xterm.js)
*/

/**
* Default xterm.js terminal colors for dark mode
*/
export const DEFAULT_TERMINAL_COLORS_DARK: TerminalColors = {
background: "#000000",
foreground: "#ffffff",
cursor: "#ffffff",
cursorAccent: "#000000",
selectionBackground: "#4d4d4d",

// Standard ANSI colors (xterm defaults)
black: "#2e3436",
red: "#cc0000",
green: "#4e9a06",
yellow: "#c4a000",
blue: "#3465a4",
magenta: "#75507b",
cyan: "#06989a",
white: "#d3d7cf",

// Bright ANSI colors (xterm defaults)
brightBlack: "#555753",
brightRed: "#ef2929",
brightGreen: "#8ae234",
brightYellow: "#fce94f",
brightBlue: "#729fcf",
brightMagenta: "#ad7fa8",
brightCyan: "#34e2e2",
brightWhite: "#eeeeec",
};

/**
* Default xterm.js terminal colors for light mode
*/
export const DEFAULT_TERMINAL_COLORS_LIGHT: TerminalColors = {
background: "#ffffff",
foreground: "#000000",
cursor: "#000000",
cursorAccent: "#ffffff",
selectionBackground: "#add6ff",

// Standard ANSI colors (xterm defaults)
black: "#2e3436",
red: "#cc0000",
green: "#4e9a06",
yellow: "#c4a000",
blue: "#3465a4",
magenta: "#75507b",
cyan: "#06989a",
white: "#d3d7cf",

// Bright ANSI colors (xterm defaults)
brightBlack: "#555753",
brightRed: "#ef2929",
brightGreen: "#8ae234",
brightYellow: "#fce94f",
brightBlue: "#729fcf",
brightMagenta: "#ad7fa8",
brightCyan: "#34e2e2",
brightWhite: "#eeeeec",
};

/**
* Get default terminal colors based on theme type
*/
export function getDefaultTerminalColors(
type: "dark" | "light",
): TerminalColors {
return type === "dark"
? DEFAULT_TERMINAL_COLORS_DARK
: DEFAULT_TERMINAL_COLORS_LIGHT;
}

/**
* Get terminal colors from a theme, falling back to defaults if not defined
*/
export function getTerminalColors(theme: Theme): TerminalColors {
return theme.terminal ?? getDefaultTerminalColors(theme.type);
}
Comment on lines +83 to +85

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getTerminalColors() returns the theme’s terminal object as-is, otherwise returns a shared default object. That makes the returned value potentially mutable shared state (especially for the defaults), and it’s easy for downstream code (or xterm integration) to accidentally mutate it and affect all consumers.

Even if the current code path doesn’t mutate, this is a sharp edge that’s hard to debug later.

Suggestion

Consider returning a defensive copy to avoid accidental mutation of the shared defaults (and to keep behavior consistent whether the source is theme.terminal or defaults).

For example:

  • return { ...(theme.terminal ?? getDefaultTerminalColors(theme.type)) };

If you want to be extra safe against nested mutation (not needed here since it’s flat), a shallow copy is sufficient.

Reply with "@CharlieHelps yes please" if you'd like me to add a commit with this change.


/**
* UI color definitions for the application chrome
* Color values should be valid CSS color strings (hex, rgb, oklch, etc.)
Expand Down Expand Up @@ -121,8 +201,8 @@ export interface Theme {

/** UI colors for app chrome */
ui: UIColors;
/** Terminal ANSI colors */
terminal: TerminalColors;
/** Terminal ANSI colors (optional, falls back to xterm defaults based on theme type) */
terminal?: TerminalColors;

/** Whether this is a built-in theme */
isBuiltIn?: boolean;
Expand Down
Loading