From 540c3e490657c4356216d9001fc44196d0390b62 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 11:07:54 +0300 Subject: [PATCH 1/8] feat(desktop): add custom font settings for terminal and editor Add configurable font family and font size settings for both terminal panels and editor/diff views in the Appearance settings page. - Add terminal_font_family, terminal_font_size, editor_font_family, editor_font_size columns to local-db settings table - Add getFontSettings/setFontSettings tRPC procedures - Add Editor Font and Terminal Font sections to Appearance settings with text input for font family, number input for size (10-24), and reset to default button - Wire up Terminal to apply custom fonts via xterm options with live updates - Wire up Monaco editor/diff views via useMonacoEditorOptions hook - Add settings search entries for font customization --- .../src/lib/trpc/routers/settings/index.ts | 59 + .../MonacoProvider/MonacoProvider.tsx | 25 +- .../providers/MonacoProvider/index.ts | 1 + .../AppearanceSettings/AppearanceSettings.tsx | 217 +++- .../utils/settings-search/settings-search.ts | 38 + .../components/DiffViewer/DiffViewer.tsx | 5 +- .../FileViewerContent/FileViewerContent.tsx | 5 +- .../TabsContent/Terminal/Terminal.tsx | 22 + .../TabsContent/Terminal/config.ts | 8 +- .../drizzle/0022_add_font_settings.sql | 4 + .../local-db/drizzle/meta/0022_snapshot.json | 1113 +++++++++++++++++ packages/local-db/drizzle/meta/_journal.json | 9 +- packages/local-db/src/schema/schema.ts | 4 + 13 files changed, 1500 insertions(+), 10 deletions(-) create mode 100644 packages/local-db/drizzle/0022_add_font_settings.sql create mode 100644 packages/local-db/drizzle/meta/0022_snapshot.json diff --git a/apps/desktop/src/lib/trpc/routers/settings/index.ts b/apps/desktop/src/lib/trpc/routers/settings/index.ts index d9ff55e10b7..1d61b8c6316 100644 --- a/apps/desktop/src/lib/trpc/routers/settings/index.ts +++ b/apps/desktop/src/lib/trpc/routers/settings/index.ts @@ -495,6 +495,65 @@ export const createSettingsRouter = () => { return { success: true }; }), + getFontSettings: publicProcedure.query(() => { + const row = getSettings(); + return { + terminalFontFamily: row.terminalFontFamily ?? null, + terminalFontSize: row.terminalFontSize ?? null, + editorFontFamily: row.editorFontFamily ?? null, + editorFontSize: row.editorFontSize ?? null, + }; + }), + + setFontSettings: publicProcedure + .input( + z.object({ + terminalFontFamily: z.string().nullable().optional(), + terminalFontSize: z + .number() + .int() + .min(10) + .max(24) + .nullable() + .optional(), + editorFontFamily: z.string().nullable().optional(), + editorFontSize: z + .number() + .int() + .min(10) + .max(24) + .nullable() + .optional(), + }), + ) + .mutation(({ input }) => { + const set: Record = {}; + + if (input.terminalFontFamily !== undefined) { + set.terminalFontFamily = input.terminalFontFamily?.trim() || null; + } + if (input.terminalFontSize !== undefined) { + set.terminalFontSize = input.terminalFontSize; + } + if (input.editorFontFamily !== undefined) { + set.editorFontFamily = input.editorFontFamily?.trim() || null; + } + if (input.editorFontSize !== undefined) { + set.editorFontSize = input.editorFontSize; + } + + localDb + .insert(settings) + .values({ id: 1, ...set }) + .onConflictDoUpdate({ + target: settings.id, + set, + }) + .run(); + + return { success: true }; + }), + // TODO: remove telemetry procedures once telemetry_enabled column is dropped getTelemetryEnabled: publicProcedure.query(() => { return true; diff --git a/apps/desktop/src/renderer/providers/MonacoProvider/MonacoProvider.tsx b/apps/desktop/src/renderer/providers/MonacoProvider/MonacoProvider.tsx index 3264e271422..65b113c9f46 100644 --- a/apps/desktop/src/renderer/providers/MonacoProvider/MonacoProvider.tsx +++ b/apps/desktop/src/renderer/providers/MonacoProvider/MonacoProvider.tsx @@ -6,7 +6,8 @@ import htmlWorker from "monaco-editor/esm/vs/language/html/html.worker?worker"; import jsonWorker from "monaco-editor/esm/vs/language/json/json.worker?worker"; import tsWorker from "monaco-editor/esm/vs/language/typescript/ts.worker?worker"; import type React from "react"; -import { createContext, useContext, useEffect, useState } from "react"; +import { createContext, useContext, useEffect, useMemo, useState } from "react"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { useMonacoTheme } from "renderer/stores/theme"; self.MonacoEnvironment = { @@ -127,6 +128,28 @@ export const MONACO_EDITOR_OPTIONS = { }, }; +export function useMonacoEditorOptions() { + const { data: fontSettings } = electronTrpc.settings.getFontSettings.useQuery( + undefined, + { + staleTime: 30_000, + }, + ); + + return useMemo(() => { + if (!fontSettings) return MONACO_EDITOR_OPTIONS; + return { + ...MONACO_EDITOR_OPTIONS, + ...(fontSettings.editorFontFamily && { + fontFamily: fontSettings.editorFontFamily, + }), + ...(fontSettings.editorFontSize != null && { + fontSize: fontSettings.editorFontSize, + }), + }; + }, [fontSettings]); +} + export function registerSaveAction( editor: monaco.editor.IStandaloneCodeEditor, onSave: () => void, diff --git a/apps/desktop/src/renderer/providers/MonacoProvider/index.ts b/apps/desktop/src/renderer/providers/MonacoProvider/index.ts index 9c0a21f3940..65656ed95f7 100644 --- a/apps/desktop/src/renderer/providers/MonacoProvider/index.ts +++ b/apps/desktop/src/renderer/providers/MonacoProvider/index.ts @@ -4,5 +4,6 @@ export { monaco, registerSaveAction, SUPERSET_THEME, + useMonacoEditorOptions, useMonacoReady, } from "./MonacoProvider"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx index 7863aa7495c..97549e9fd66 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx @@ -1,3 +1,5 @@ +import { Button } from "@superset/ui/button"; +import { Input } from "@superset/ui/input"; import { Select, SelectContent, @@ -5,6 +7,8 @@ import { SelectTrigger, SelectValue, } from "@superset/ui/select"; +import { useCallback } from "react"; +import { electronTrpc } from "renderer/lib/electron-trpc"; import { type MarkdownStyle, SYSTEM_THEME_ID, @@ -23,6 +27,13 @@ import { import { SystemThemeCard } from "./components/SystemThemeCard"; import { ThemeCard } from "./components/ThemeCard"; +const DEFAULT_EDITOR_FONT_FAMILY = + "ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace"; +const DEFAULT_EDITOR_FONT_SIZE = 13; +const DEFAULT_TERMINAL_FONT_FAMILY = + "MesloLGM Nerd Font, MesloLGM NF, Menlo, Monaco, monospace"; +const DEFAULT_TERMINAL_FONT_SIZE = 14; + interface AppearanceSettingsProps { visibleItems?: SettingItemId[] | null; } @@ -40,6 +51,14 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { SETTING_ITEM_ID.APPEARANCE_CUSTOM_THEMES, visibleItems, ); + const showEditorFont = isItemVisible( + SETTING_ITEM_ID.APPEARANCE_EDITOR_FONT, + visibleItems, + ); + const showTerminalFont = isItemVisible( + SETTING_ITEM_ID.APPEARANCE_TERMINAL_FONT, + visibleItems, + ); const activeThemeId = useThemeId(); const setTheme = useSetTheme(); @@ -49,6 +68,76 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { const allThemes = [...builtInThemes, ...customThemes]; + const utils = electronTrpc.useUtils(); + + const { data: fontSettings, isLoading: isFontLoading } = + electronTrpc.settings.getFontSettings.useQuery(); + + const setFontSettings = electronTrpc.settings.setFontSettings.useMutation({ + onMutate: async (input) => { + await utils.settings.getFontSettings.cancel(); + const previous = utils.settings.getFontSettings.getData(); + utils.settings.getFontSettings.setData(undefined, (old) => ({ + terminalFontFamily: old?.terminalFontFamily ?? null, + terminalFontSize: old?.terminalFontSize ?? null, + editorFontFamily: old?.editorFontFamily ?? null, + editorFontSize: old?.editorFontSize ?? null, + ...input, + })); + return { previous }; + }, + onError: (_err, _vars, context) => { + if (context?.previous !== undefined) { + utils.settings.getFontSettings.setData(undefined, context.previous); + } + }, + onSettled: () => { + utils.settings.getFontSettings.invalidate(); + }, + }); + + const handleEditorFontFamilyBlur = useCallback( + (e: React.FocusEvent) => { + const value = e.target.value.trim(); + setFontSettings.mutate({ + editorFontFamily: value || null, + }); + }, + [setFontSettings], + ); + + const handleEditorFontSizeChange = useCallback( + (e: React.ChangeEvent) => { + const value = Number.parseInt(e.target.value, 10); + if (!Number.isNaN(value) && value >= 10 && value <= 24) { + setFontSettings.mutate({ editorFontSize: value }); + } + }, + [setFontSettings], + ); + + const handleTerminalFontFamilyBlur = useCallback( + (e: React.FocusEvent) => { + const value = e.target.value.trim(); + setFontSettings.mutate({ + terminalFontFamily: value || null, + }); + }, + [setFontSettings], + ); + + const handleTerminalFontSizeChange = useCallback( + (e: React.ChangeEvent) => { + const value = Number.parseInt(e.target.value, 10); + if (!Number.isNaN(value) && value >= 10 && value <= 24) { + setFontSettings.mutate({ terminalFontSize: value }); + } + }, + [setFontSettings], + ); + + const hasPrecedingSection = showTheme || showMarkdown; + return (
@@ -107,8 +196,134 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) {
)} + {showEditorFont && ( +
+

Editor Font

+

+ Font used in diff views and file editors +

+
+
+ + Font Family + + +
+
+
+ + Font Size + + +
+ {(fontSettings?.editorFontFamily || + fontSettings?.editorFontSize) && ( + + )} +
+
+
+ )} + + {showTerminalFont && ( +
+

Terminal Font

+

+ Font used in terminal panels. Nerd Fonts recommended for shell + theme icons. +

+
+
+ + Font Family + + +
+
+
+ + Font Size + + +
+ {(fontSettings?.terminalFontFamily || + fontSettings?.terminalFontSize) && ( + + )} +
+
+
+ )} + {showCustomThemes && ( -
+

Custom Themes

Custom theme import coming soon. You'll be able to import JSON diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts index b69f8c9093e..89ef2fbbb29 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts @@ -15,6 +15,8 @@ export const SETTING_ITEM_ID = { APPEARANCE_THEME: "appearance-theme", APPEARANCE_MARKDOWN: "appearance-markdown", APPEARANCE_CUSTOM_THEMES: "appearance-custom-themes", + APPEARANCE_EDITOR_FONT: "appearance-editor-font", + APPEARANCE_TERMINAL_FONT: "appearance-terminal-font", RINGTONES_NOTIFICATION: "ringtones-notification", @@ -251,6 +253,42 @@ export const SETTINGS_ITEMS: SettingsItem[] = [ "customize", ], }, + { + id: SETTING_ITEM_ID.APPEARANCE_EDITOR_FONT, + section: "appearance", + title: "Editor Font", + description: "Font used in diff views and file editors", + keywords: [ + "appearance", + "font", + "family", + "size", + "editor", + "diff", + "mono", + "monospace", + "typography", + "custom", + ], + }, + { + id: SETTING_ITEM_ID.APPEARANCE_TERMINAL_FONT, + section: "appearance", + title: "Terminal Font", + description: "Font used in terminal panels", + keywords: [ + "appearance", + "font", + "family", + "size", + "terminal", + "mono", + "monospace", + "typography", + "custom", + "nerd", + ], + }, { id: SETTING_ITEM_ID.RINGTONES_NOTIFICATION, section: "ringtones", diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/DiffViewer/DiffViewer.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/DiffViewer/DiffViewer.tsx index 483d9489caa..09342e7bc36 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/DiffViewer/DiffViewer.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ChangesContent/components/DiffViewer/DiffViewer.tsx @@ -3,9 +3,9 @@ import type * as Monaco from "monaco-editor"; import { useCallback, useEffect, useRef, useState } from "react"; import { LuLoader } from "react-icons/lu"; import { - MONACO_EDITOR_OPTIONS, registerSaveAction, SUPERSET_THEME, + useMonacoEditorOptions, useMonacoReady, } from "renderer/providers/MonacoProvider"; import type { Tab } from "renderer/stores/tabs/types"; @@ -72,6 +72,7 @@ export function DiffViewer({ fitContent = false, }: DiffViewerProps) { const isMonacoReady = useMonacoReady(); + const monacoEditorOptions = useMonacoEditorOptions(); const diffEditorRef = useRef( null, ); @@ -272,7 +273,7 @@ export function DiffViewer({

} options={{ - ...MONACO_EDITOR_OPTIONS, + ...monacoEditorOptions, lineNumbersMinChars: getLineNumbersMinChars( contents.original, contents.modified, diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx index e6d852f2dd2..7946b1ccc68 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/TabView/FileViewerPane/components/FileViewerContent/FileViewerContent.tsx @@ -4,9 +4,9 @@ import { type MutableRefObject, useCallback, useEffect, useRef } from "react"; import { LuLoader } from "react-icons/lu"; import { MarkdownRenderer } from "renderer/components/MarkdownRenderer"; import { - MONACO_EDITOR_OPTIONS, registerSaveAction, SUPERSET_THEME, + useMonacoEditorOptions, useMonacoReady, } from "renderer/providers/MonacoProvider"; import type { Tab } from "renderer/stores/tabs/types"; @@ -124,6 +124,7 @@ export function FileViewerContent({ }: FileViewerContentProps) { const isImage = isImageFile(filePath); const isMonacoReady = useMonacoReady(); + const monacoEditorOptions = useMonacoEditorOptions(); const hasAppliedInitialLocationRef = useRef(false); // biome-ignore lint/correctness/useExhaustiveDependencies: Reset on file change only @@ -346,7 +347,7 @@ export function FileViewerContent({
} options={{ - ...MONACO_EDITOR_OPTIONS, + ...monacoEditorOptions, contextmenu: false, // Disable Monaco's native context menu to use our custom one }} /> diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx index c56d766da75..5961cf81146 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx @@ -7,6 +7,10 @@ import { electronTrpc } from "renderer/lib/electron-trpc"; import { useTabsStore } from "renderer/stores/tabs/store"; import { useTerminalTheme } from "renderer/stores/theme"; import { ConnectionErrorOverlay, SessionKilledOverlay } from "./components"; +import { + DEFAULT_TERMINAL_FONT_FAMILY, + DEFAULT_TERMINAL_FONT_SIZE, +} from "./config"; import { getDefaultTerminalBg, type TerminalRendererRef } from "./helpers"; import { useFileLinkClick, @@ -303,6 +307,24 @@ export const Terminal = ({ paneId, tabId, workspaceId }: TerminalProps) => { xterm.options.theme = terminalTheme; }, [terminalTheme]); + const { data: fontSettings } = electronTrpc.settings.getFontSettings.useQuery( + undefined, + { + staleTime: 30_000, + }, + ); + + useEffect(() => { + const xterm = xtermRef.current; + if (!xterm || !fontSettings) return; + const family = + fontSettings.terminalFontFamily || DEFAULT_TERMINAL_FONT_FAMILY; + const size = fontSettings.terminalFontSize ?? DEFAULT_TERMINAL_FONT_SIZE; + xterm.options.fontFamily = family; + xterm.options.fontSize = size; + fitAddonRef.current?.fit(); + }, [fontSettings]); + const terminalBg = terminalTheme?.background ?? getDefaultTerminalBg(); const handleDragOver = (event: React.DragEvent) => { diff --git a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts index 33bb8cdc195..a9fed0033fe 100644 --- a/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts +++ b/apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts @@ -13,7 +13,7 @@ export const DEBUG_TERMINAL = localStorage.getItem("SUPERSET_TERMINAL_DEBUG") === "1"; // Nerd Fonts first for shell theme compatibility (Oh My Posh, Powerlevel10k, etc.) -const TERMINAL_FONT_FAMILY = [ +export const DEFAULT_TERMINAL_FONT_FAMILY = [ "MesloLGM Nerd Font", "MesloLGM NF", "MesloLGS NF", @@ -31,10 +31,12 @@ const TERMINAL_FONT_FAMILY = [ "monospace", ].join(", "); +export const DEFAULT_TERMINAL_FONT_SIZE = 14; + export const TERMINAL_OPTIONS: ITerminalOptions = { cursorBlink: true, - fontSize: 14, - fontFamily: TERMINAL_FONT_FAMILY, + fontSize: DEFAULT_TERMINAL_FONT_SIZE, + fontFamily: DEFAULT_TERMINAL_FONT_FAMILY, theme: TERMINAL_THEME, allowProposedApi: true, scrollback: 10000, diff --git a/packages/local-db/drizzle/0022_add_font_settings.sql b/packages/local-db/drizzle/0022_add_font_settings.sql new file mode 100644 index 00000000000..2e0fd82303d --- /dev/null +++ b/packages/local-db/drizzle/0022_add_font_settings.sql @@ -0,0 +1,4 @@ +ALTER TABLE `settings` ADD `terminal_font_family` text;--> statement-breakpoint +ALTER TABLE `settings` ADD `terminal_font_size` integer;--> statement-breakpoint +ALTER TABLE `settings` ADD `editor_font_family` text;--> statement-breakpoint +ALTER TABLE `settings` ADD `editor_font_size` integer; \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/0022_snapshot.json b/packages/local-db/drizzle/meta/0022_snapshot.json new file mode 100644 index 00000000000..ac8353d9e8f --- /dev/null +++ b/packages/local-db/drizzle/meta/0022_snapshot.json @@ -0,0 +1,1113 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "a1b2c3d4-font-sett-ings-snapshot000021", + "prevId": "8a53a71d-3904-41e1-8c3f-b0322d375a78", + "tables": { + "organization_members": { + "name": "organization_members", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "user_id": { + "name": "user_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "role": { + "name": "role", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organization_members_organization_id_idx": { + "name": "organization_members_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "organization_members_user_id_idx": { + "name": "organization_members_user_id_idx", + "columns": [ + "user_id" + ], + "isUnique": false + } + }, + "foreignKeys": { + "organization_members_organization_id_organizations_id_fk": { + "name": "organization_members_organization_id_organizations_id_fk", + "tableFrom": "organization_members", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "organization_members_user_id_users_id_fk": { + "name": "organization_members_user_id_users_id_fk", + "tableFrom": "organization_members", + "tableTo": "users", + "columnsFrom": [ + "user_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "organizations": { + "name": "organizations", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_org_id": { + "name": "clerk_org_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "github_org": { + "name": "github_org", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "organizations_clerk_org_id_unique": { + "name": "organizations_clerk_org_id_unique", + "columns": [ + "clerk_org_id" + ], + "isUnique": true + }, + "organizations_slug_unique": { + "name": "organizations_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "organizations_slug_idx": { + "name": "organizations_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "organizations_clerk_org_id_idx": { + "name": "organizations_clerk_org_id_idx", + "columns": [ + "clerk_org_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "projects": { + "name": "projects", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "main_repo_path": { + "name": "main_repo_path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "color": { + "name": "color", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "config_toast_dismissed": { + "name": "config_toast_dismissed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "default_branch": { + "name": "default_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_owner": { + "name": "github_owner", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "hide_image": { + "name": "hide_image", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "projects_main_repo_path_idx": { + "name": "projects_main_repo_path_idx", + "columns": [ + "main_repo_path" + ], + "isUnique": false + }, + "projects_last_opened_at_idx": { + "name": "projects_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "settings": { + "name": "settings", + "columns": { + "id": { + "name": "id", + "type": "integer", + "primaryKey": true, + "notNull": true, + "autoincrement": false, + "default": 1 + }, + "last_active_workspace_id": { + "name": "last_active_workspace_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_used_app": { + "name": "last_used_app", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets": { + "name": "terminal_presets", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_presets_initialized": { + "name": "terminal_presets_initialized", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "selected_ringtone_id": { + "name": "selected_ringtone_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "active_organization_id": { + "name": "active_organization_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "confirm_on_quit": { + "name": "confirm_on_quit", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_link_behavior": { + "name": "terminal_link_behavior", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "persist_terminal": { + "name": "persist_terminal", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": true + }, + "auto_apply_default_preset": { + "name": "auto_apply_default_preset", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_mode": { + "name": "branch_prefix_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch_prefix_custom": { + "name": "branch_prefix_custom", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "notification_sounds_muted": { + "name": "notification_sounds_muted", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "delete_local_branch": { + "name": "delete_local_branch", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "file_open_mode": { + "name": "file_open_mode", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_font_family": { + "name": "terminal_font_family", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "terminal_font_size": { + "name": "terminal_font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "editor_font_family": { + "name": "editor_font_family", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "editor_font_size": { + "name": "editor_font_size", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": {}, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "tasks": { + "name": "tasks", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "slug": { + "name": "slug", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "title": { + "name": "title", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "description": { + "name": "description", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status": { + "name": "status", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "status_color": { + "name": "status_color", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_type": { + "name": "status_type", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "status_position": { + "name": "status_position", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "priority": { + "name": "priority", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "organization_id": { + "name": "organization_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "repository_id": { + "name": "repository_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "assignee_id": { + "name": "assignee_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "creator_id": { + "name": "creator_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "estimate": { + "name": "estimate", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "due_date": { + "name": "due_date", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "labels": { + "name": "labels", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "pr_url": { + "name": "pr_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_provider": { + "name": "external_provider", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_id": { + "name": "external_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_key": { + "name": "external_key", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "external_url": { + "name": "external_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "last_synced_at": { + "name": "last_synced_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "sync_error": { + "name": "sync_error", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "started_at": { + "name": "started_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "completed_at": { + "name": "completed_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "tasks_slug_unique": { + "name": "tasks_slug_unique", + "columns": [ + "slug" + ], + "isUnique": true + }, + "tasks_slug_idx": { + "name": "tasks_slug_idx", + "columns": [ + "slug" + ], + "isUnique": false + }, + "tasks_organization_id_idx": { + "name": "tasks_organization_id_idx", + "columns": [ + "organization_id" + ], + "isUnique": false + }, + "tasks_assignee_id_idx": { + "name": "tasks_assignee_id_idx", + "columns": [ + "assignee_id" + ], + "isUnique": false + }, + "tasks_status_idx": { + "name": "tasks_status_idx", + "columns": [ + "status" + ], + "isUnique": false + }, + "tasks_created_at_idx": { + "name": "tasks_created_at_idx", + "columns": [ + "created_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "tasks_organization_id_organizations_id_fk": { + "name": "tasks_organization_id_organizations_id_fk", + "tableFrom": "tasks", + "tableTo": "organizations", + "columnsFrom": [ + "organization_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "tasks_assignee_id_users_id_fk": { + "name": "tasks_assignee_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "assignee_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "set null", + "onUpdate": "no action" + }, + "tasks_creator_id_users_id_fk": { + "name": "tasks_creator_id_users_id_fk", + "tableFrom": "tasks", + "tableTo": "users", + "columnsFrom": [ + "creator_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "users": { + "name": "users", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "clerk_id": { + "name": "clerk_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "email": { + "name": "email", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "avatar_url": { + "name": "avatar_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "deleted_at": { + "name": "deleted_at", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + } + }, + "indexes": { + "users_clerk_id_unique": { + "name": "users_clerk_id_unique", + "columns": [ + "clerk_id" + ], + "isUnique": true + }, + "users_email_unique": { + "name": "users_email_unique", + "columns": [ + "email" + ], + "isUnique": true + }, + "users_email_idx": { + "name": "users_email_idx", + "columns": [ + "email" + ], + "isUnique": false + }, + "users_clerk_id_idx": { + "name": "users_clerk_id_idx", + "columns": [ + "clerk_id" + ], + "isUnique": false + } + }, + "foreignKeys": {}, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "workspaces": { + "name": "workspaces", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "worktree_id": { + "name": "worktree_id", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "type": { + "name": "type", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "name": { + "name": "name", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "tab_order": { + "name": "tab_order", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "updated_at": { + "name": "updated_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "last_opened_at": { + "name": "last_opened_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "is_unread": { + "name": "is_unread", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "is_unnamed": { + "name": "is_unnamed", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false, + "default": false + }, + "deleting_at": { + "name": "deleting_at", + "type": "integer", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "workspaces_project_id_idx": { + "name": "workspaces_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "workspaces_worktree_id_idx": { + "name": "workspaces_worktree_id_idx", + "columns": [ + "worktree_id" + ], + "isUnique": false + }, + "workspaces_last_opened_at_idx": { + "name": "workspaces_last_opened_at_idx", + "columns": [ + "last_opened_at" + ], + "isUnique": false + } + }, + "foreignKeys": { + "workspaces_project_id_projects_id_fk": { + "name": "workspaces_project_id_projects_id_fk", + "tableFrom": "workspaces", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + }, + "workspaces_worktree_id_worktrees_id_fk": { + "name": "workspaces_worktree_id_worktrees_id_fk", + "tableFrom": "workspaces", + "tableTo": "worktrees", + "columnsFrom": [ + "worktree_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + }, + "worktrees": { + "name": "worktrees", + "columns": { + "id": { + "name": "id", + "type": "text", + "primaryKey": true, + "notNull": true, + "autoincrement": false + }, + "project_id": { + "name": "project_id", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "path": { + "name": "path", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "branch": { + "name": "branch", + "type": "text", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "base_branch": { + "name": "base_branch", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "created_at": { + "name": "created_at", + "type": "integer", + "primaryKey": false, + "notNull": true, + "autoincrement": false + }, + "git_status": { + "name": "git_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + }, + "github_status": { + "name": "github_status", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false + } + }, + "indexes": { + "worktrees_project_id_idx": { + "name": "worktrees_project_id_idx", + "columns": [ + "project_id" + ], + "isUnique": false + }, + "worktrees_branch_idx": { + "name": "worktrees_branch_idx", + "columns": [ + "branch" + ], + "isUnique": false + } + }, + "foreignKeys": { + "worktrees_project_id_projects_id_fk": { + "name": "worktrees_project_id_projects_id_fk", + "tableFrom": "worktrees", + "tableTo": "projects", + "columnsFrom": [ + "project_id" + ], + "columnsTo": [ + "id" + ], + "onDelete": "cascade", + "onUpdate": "no action" + } + }, + "compositePrimaryKeys": {}, + "uniqueConstraints": {}, + "checkConstraints": {} + } + }, + "views": {}, + "enums": {}, + "_meta": { + "schemas": {}, + "tables": {}, + "columns": {} + }, + "internal": { + "indexes": {} + } +} diff --git a/packages/local-db/drizzle/meta/_journal.json b/packages/local-db/drizzle/meta/_journal.json index b2454c0233f..89ee3c96c1d 100644 --- a/packages/local-db/drizzle/meta/_journal.json +++ b/packages/local-db/drizzle/meta/_journal.json @@ -155,6 +155,13 @@ "when": 1770783531923, "tag": "0021_add_image_project", "breakpoints": true + }, + { + "idx": 22, + "version": "6", + "when": 1770823982765, + "tag": "0022_add_font_settings", + "breakpoints": true } ] -} \ No newline at end of file +} diff --git a/packages/local-db/src/schema/schema.ts b/packages/local-db/src/schema/schema.ts index 73137d2d064..04e47d73864 100644 --- a/packages/local-db/src/schema/schema.ts +++ b/packages/local-db/src/schema/schema.ts @@ -160,6 +160,10 @@ export const settings = sqliteTable("settings", { }), deleteLocalBranch: integer("delete_local_branch", { mode: "boolean" }), fileOpenMode: text("file_open_mode").$type(), + terminalFontFamily: text("terminal_font_family"), + terminalFontSize: integer("terminal_font_size"), + editorFontFamily: text("editor_font_family"), + editorFontSize: integer("editor_font_size"), }); export type InsertSettings = typeof settings.$inferInsert; From a5f5f70298a4b078b4b465bccb6f798b6fa5db14 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 11:24:59 +0300 Subject: [PATCH 2/8] feat(appearance): improve font settings layout with side-by-side inputs and live preview --- .../AppearanceSettings/AppearanceSettings.tsx | 245 +++++++++++------- 1 file changed, 145 insertions(+), 100 deletions(-) diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx index 97549e9fd66..7dc65b1d58d 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx @@ -7,7 +7,7 @@ import { SelectTrigger, SelectValue, } from "@superset/ui/select"; -import { useCallback } from "react"; +import { useCallback, useState } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { type MarkdownStyle, @@ -34,6 +34,36 @@ const DEFAULT_TERMINAL_FONT_FAMILY = "MesloLGM Nerd Font, MesloLGM NF, Menlo, Monaco, monospace"; const DEFAULT_TERMINAL_FONT_SIZE = 14; +const FONT_PREVIEW_TEXT = + "The quick brown fox jumps over the lazy dog.\n0O1lI {}[]() => !== +- @#$%"; + +function FontPreview({ + fontFamily, + fontSize, + variant, +}: { + fontFamily: string; + fontSize: number; + variant: "editor" | "terminal"; +}) { + const isTerminal = variant === "terminal"; + return ( +
+ {FONT_PREVIEW_TEXT} +
+ ); +} + interface AppearanceSettingsProps { visibleItems?: SettingItemId[] | null; } @@ -136,6 +166,24 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { [setFontSettings], ); + const [editorFontDraft, setEditorFontDraft] = useState(null); + const [terminalFontDraft, setTerminalFontDraft] = useState( + null, + ); + + const editorPreviewFamily = + editorFontDraft ?? + fontSettings?.editorFontFamily ?? + DEFAULT_EDITOR_FONT_FAMILY; + const editorPreviewSize = + fontSettings?.editorFontSize ?? DEFAULT_EDITOR_FONT_SIZE; + const terminalPreviewFamily = + terminalFontDraft ?? + fontSettings?.terminalFontFamily ?? + DEFAULT_TERMINAL_FONT_FAMILY; + const terminalPreviewSize = + fontSettings?.terminalFontSize ?? DEFAULT_TERMINAL_FONT_SIZE; + const hasPrecedingSection = showTheme || showMarkdown; return ( @@ -198,57 +246,55 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { {showEditorFont && (
-

Editor Font

-

+

Editor Font

+

Font used in diff views and file editors

-
-
- - Font Family - - -
-
-
- - Font Size - - -
- {(fontSettings?.editorFontFamily || - fontSettings?.editorFontSize) && ( - - )} -
+
+ setEditorFontDraft(e.target.value)} + onBlur={(e) => { + handleEditorFontFamilyBlur(e); + setEditorFontDraft(null); + }} + disabled={isFontLoading} + className="flex-1" + /> + + {(fontSettings?.editorFontFamily || + fontSettings?.editorFontSize) && ( + + )} +
+
+
)} @@ -259,59 +305,58 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { hasPrecedingSection || showEditorFont ? "pt-6 border-t" : "" } > -

Terminal Font

-

+

Terminal Font

+

Font used in terminal panels. Nerd Fonts recommended for shell theme icons.

-
-
- - Font Family - - -
-
-
- - Font Size - - -
- {(fontSettings?.terminalFontFamily || - fontSettings?.terminalFontSize) && ( - - )} -
+
+ setTerminalFontDraft(e.target.value)} + onBlur={(e) => { + handleTerminalFontFamilyBlur(e); + setTerminalFontDraft(null); + }} + disabled={isFontLoading} + className="flex-1" + /> + + {(fontSettings?.terminalFontFamily || + fontSettings?.terminalFontSize) && ( + + )} +
+
+
)} From e4bb95a9cd22ace226ebeb880ab8122ba02ecec5 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 11:28:27 +0300 Subject: [PATCH 3/8] feat(appearance): make Nerd Fonts a hyperlink in terminal font description --- .claude-task-prompt.md | 78 +++++++++++++++++++ .../AppearanceSettings/AppearanceSettings.tsx | 12 ++- 2 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 .claude-task-prompt.md diff --git a/.claude-task-prompt.md b/.claude-task-prompt.md new file mode 100644 index 00000000000..772cbb9a274 --- /dev/null +++ b/.claude-task-prompt.md @@ -0,0 +1,78 @@ +You are working on the Superset desktop app (Electron + React + xterm.js + Monaco). The repo is at /tmp/superset with remote "fork" pointing to uinafdev/superset on GitHub. + +TASK: Implement custom font settings for Terminal and Editor/Diff views in the Appearance settings page. + +CONTEXT: Currently fonts are hardcoded: +- Terminal: MesloLGM Nerd Font stack, 14px (apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts) +- Editor/Diff: ui-monospace stack, 13px (apps/desktop/src/renderer/providers/MonacoProvider/MonacoProvider.tsx) + +The settings table is in packages/local-db/src/schema/schema.ts (Drizzle ORM, SQLite). +Settings router: apps/desktop/src/lib/trpc/routers/settings/index.ts +Appearance page: apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx +Settings search: apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts +Theme store: apps/desktop/src/renderer/stores/theme/store.ts + +IMPLEMENTATION PLAN: + +1. DB SCHEMA - Add 4 nullable columns to settings table in packages/local-db/src/schema/schema.ts: + - terminal_font_family (text) + - terminal_font_size (integer) + - editor_font_family (text) + - editor_font_size (integer) + Null = use current defaults. Create a Drizzle migration. + +2. SETTINGS ROUTER - Add to apps/desktop/src/lib/trpc/routers/settings/index.ts: + - getFontSettings: returns { terminalFontFamily, terminalFontSize, editorFontFamily, editorFontSize } + - setFontSettings: validates and persists (font size clamped 10-24, font family trimmed) + +3. SETTINGS SEARCH - Add to settings-search.ts: + - APPEARANCE_EDITOR_FONT and APPEARANCE_TERMINAL_FONT item IDs + - Keywords: font, family, size, terminal, editor, diff, mono, monospace, typography, custom + +4. APPEARANCE UI - In AppearanceSettings.tsx, after Markdown Style section, before Custom Themes: + Add two new sections with border-t separators: + + "Editor Font" section: + - Font Family: text input, placeholder="ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace" + - Font Size: number input (spinner), default 13, min 10, max 24 + + "Terminal Font" section: + - Font Family: text input, placeholder="MesloLGM Nerd Font, MesloLGM NF, Menlo, Monaco, monospace" + - Font Size: number input (spinner), default 14, min 10, max 24 + + Use the same UI patterns as the existing sections (h3 headings, text-sm text-muted-foreground descriptions, proper spacing). Use shadcn Input component for text, and a number input for size. + + Each should have a "Reset to default" button that clears the custom value. + + Add description text: + - Editor: "Font used in diff views and file editors" + - Terminal: "Font used in terminal panels. Nerd Fonts recommended for shell theme icons." + +5. CONSUMERS - Make terminal and Monaco read from settings: + + Terminal (config.ts + helpers.ts): + - Export a function that fetches font settings and merges with TERMINAL_OPTIONS + - In createTerminalInstance, fetch settings and override fontFamily/fontSize if set + - Subscribe to setting changes and update live via xterm.options.fontFamily / xterm.options.fontSize + fitAddon.fit() + + Monaco (MonacoProvider.tsx): + - Read font settings on init, override MONACO_EDITOR_OPTIONS if custom values set + - The existing MONACO_EDITOR_OPTIONS should be used as defaults + + For fetching settings in the renderer, use the existing trpc client pattern (electronTrpcClient). + +6. GIT: + - You are already on branch feat/custom-font-settings + - Use conventional commits + - Push to fork remote (NOT origin) + - Create PR on the fork: gh pr create --repo uinafdev/superset --base main --head feat/custom-font-settings --title "feat(appearance): custom font settings for terminal and editor" --body "..." + +IMPORTANT NOTES: +- The project uses TypeScript, React, Zustand, tRPC, Drizzle ORM, Tailwind CSS, shadcn/ui components +- Look at existing settings patterns (like terminalLinkBehavior, confirmOnQuit) for how to add new settings +- The UI components from @superset/ui (Input, Select, etc.) - check what is available +- Keep backwards compatible - null values = use existing hardcoded defaults +- Do NOT modify the upstream origin remote +- Push to "fork" remote only + +START by reading the existing files to understand the patterns, then implement each step. diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx index 7dc65b1d58d..17539b8a150 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx @@ -307,8 +307,16 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { >

Terminal Font

- Font used in terminal panels. Nerd Fonts recommended for shell - theme icons. + Font used in terminal panels.{" "} + + Nerd Fonts + {" "} + recommended for shell theme icons.

Date: Mon, 9 Feb 2026 11:40:53 +0300 Subject: [PATCH 4/8] chore: remove claude task prompt --- .claude-task-prompt.md | 78 ------------------------------------------ 1 file changed, 78 deletions(-) delete mode 100644 .claude-task-prompt.md diff --git a/.claude-task-prompt.md b/.claude-task-prompt.md deleted file mode 100644 index 772cbb9a274..00000000000 --- a/.claude-task-prompt.md +++ /dev/null @@ -1,78 +0,0 @@ -You are working on the Superset desktop app (Electron + React + xterm.js + Monaco). The repo is at /tmp/superset with remote "fork" pointing to uinafdev/superset on GitHub. - -TASK: Implement custom font settings for Terminal and Editor/Diff views in the Appearance settings page. - -CONTEXT: Currently fonts are hardcoded: -- Terminal: MesloLGM Nerd Font stack, 14px (apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config.ts) -- Editor/Diff: ui-monospace stack, 13px (apps/desktop/src/renderer/providers/MonacoProvider/MonacoProvider.tsx) - -The settings table is in packages/local-db/src/schema/schema.ts (Drizzle ORM, SQLite). -Settings router: apps/desktop/src/lib/trpc/routers/settings/index.ts -Appearance page: apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx -Settings search: apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts -Theme store: apps/desktop/src/renderer/stores/theme/store.ts - -IMPLEMENTATION PLAN: - -1. DB SCHEMA - Add 4 nullable columns to settings table in packages/local-db/src/schema/schema.ts: - - terminal_font_family (text) - - terminal_font_size (integer) - - editor_font_family (text) - - editor_font_size (integer) - Null = use current defaults. Create a Drizzle migration. - -2. SETTINGS ROUTER - Add to apps/desktop/src/lib/trpc/routers/settings/index.ts: - - getFontSettings: returns { terminalFontFamily, terminalFontSize, editorFontFamily, editorFontSize } - - setFontSettings: validates and persists (font size clamped 10-24, font family trimmed) - -3. SETTINGS SEARCH - Add to settings-search.ts: - - APPEARANCE_EDITOR_FONT and APPEARANCE_TERMINAL_FONT item IDs - - Keywords: font, family, size, terminal, editor, diff, mono, monospace, typography, custom - -4. APPEARANCE UI - In AppearanceSettings.tsx, after Markdown Style section, before Custom Themes: - Add two new sections with border-t separators: - - "Editor Font" section: - - Font Family: text input, placeholder="ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace" - - Font Size: number input (spinner), default 13, min 10, max 24 - - "Terminal Font" section: - - Font Family: text input, placeholder="MesloLGM Nerd Font, MesloLGM NF, Menlo, Monaco, monospace" - - Font Size: number input (spinner), default 14, min 10, max 24 - - Use the same UI patterns as the existing sections (h3 headings, text-sm text-muted-foreground descriptions, proper spacing). Use shadcn Input component for text, and a number input for size. - - Each should have a "Reset to default" button that clears the custom value. - - Add description text: - - Editor: "Font used in diff views and file editors" - - Terminal: "Font used in terminal panels. Nerd Fonts recommended for shell theme icons." - -5. CONSUMERS - Make terminal and Monaco read from settings: - - Terminal (config.ts + helpers.ts): - - Export a function that fetches font settings and merges with TERMINAL_OPTIONS - - In createTerminalInstance, fetch settings and override fontFamily/fontSize if set - - Subscribe to setting changes and update live via xterm.options.fontFamily / xterm.options.fontSize + fitAddon.fit() - - Monaco (MonacoProvider.tsx): - - Read font settings on init, override MONACO_EDITOR_OPTIONS if custom values set - - The existing MONACO_EDITOR_OPTIONS should be used as defaults - - For fetching settings in the renderer, use the existing trpc client pattern (electronTrpcClient). - -6. GIT: - - You are already on branch feat/custom-font-settings - - Use conventional commits - - Push to fork remote (NOT origin) - - Create PR on the fork: gh pr create --repo uinafdev/superset --base main --head feat/custom-font-settings --title "feat(appearance): custom font settings for terminal and editor" --body "..." - -IMPORTANT NOTES: -- The project uses TypeScript, React, Zustand, tRPC, Drizzle ORM, Tailwind CSS, shadcn/ui components -- Look at existing settings patterns (like terminalLinkBehavior, confirmOnQuit) for how to add new settings -- The UI components from @superset/ui (Input, Select, etc.) - check what is available -- Keep backwards compatible - null values = use existing hardcoded defaults -- Do NOT modify the upstream origin remote -- Push to "fork" remote only - -START by reading the existing files to understand the patterns, then implement each step. From 37cd346e25491a654649eba423ed1bdc327c23c5 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 11:42:29 +0300 Subject: [PATCH 5/8] test(desktop): add tests for custom font settings --- .../routers/settings/font-settings.test.ts | 219 ++++++++++++++++++ .../settings-search/settings-search.test.ts | 65 ++++++ 2 files changed, 284 insertions(+) create mode 100644 apps/desktop/src/lib/trpc/routers/settings/font-settings.test.ts create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts diff --git a/apps/desktop/src/lib/trpc/routers/settings/font-settings.test.ts b/apps/desktop/src/lib/trpc/routers/settings/font-settings.test.ts new file mode 100644 index 00000000000..086e29f6803 --- /dev/null +++ b/apps/desktop/src/lib/trpc/routers/settings/font-settings.test.ts @@ -0,0 +1,219 @@ +import { describe, expect, it } from "bun:test"; +import { z } from "zod"; + +/** + * Font settings validation tests. + * + * These test the Zod validation schema and the transformation logic used + * by the setFontSettings tRPC procedure, extracted here for unit testing + * without requiring a database connection. + */ + +// Replicate the exact Zod schema from the router +const setFontSettingsSchema = z.object({ + terminalFontFamily: z.string().nullable().optional(), + terminalFontSize: z.number().int().min(10).max(24).nullable().optional(), + editorFontFamily: z.string().nullable().optional(), + editorFontSize: z.number().int().min(10).max(24).nullable().optional(), +}); + +// Replicate the transformation logic from the mutation +function transformFontSettings( + input: z.infer, +): Record { + const set: Record = {}; + + if (input.terminalFontFamily !== undefined) { + set.terminalFontFamily = input.terminalFontFamily?.trim() || null; + } + if (input.terminalFontSize !== undefined) { + set.terminalFontSize = input.terminalFontSize; + } + if (input.editorFontFamily !== undefined) { + set.editorFontFamily = input.editorFontFamily?.trim() || null; + } + if (input.editorFontSize !== undefined) { + set.editorFontSize = input.editorFontSize; + } + + return set; +} + +describe("font settings validation", () => { + describe("getFontSettings defaults", () => { + it("returns all null when no settings are stored", () => { + // Simulates the default return shape of getFontSettings + const defaults = { + terminalFontFamily: null, + terminalFontSize: null, + editorFontFamily: null, + editorFontSize: null, + }; + + expect(defaults.terminalFontFamily).toBeNull(); + expect(defaults.terminalFontSize).toBeNull(); + expect(defaults.editorFontFamily).toBeNull(); + expect(defaults.editorFontSize).toBeNull(); + }); + }); + + describe("font size validation (range 10-24)", () => { + it("accepts font size at minimum (10)", () => { + const result = setFontSettingsSchema.safeParse({ + terminalFontSize: 10, + }); + expect(result.success).toBe(true); + }); + + it("accepts font size at maximum (24)", () => { + const result = setFontSettingsSchema.safeParse({ + editorFontSize: 24, + }); + expect(result.success).toBe(true); + }); + + it("accepts font size in the middle of range", () => { + const result = setFontSettingsSchema.safeParse({ + terminalFontSize: 14, + editorFontSize: 16, + }); + expect(result.success).toBe(true); + }); + + it("rejects font size below minimum (< 10)", () => { + const result = setFontSettingsSchema.safeParse({ + terminalFontSize: 9, + }); + expect(result.success).toBe(false); + }); + + it("rejects font size of 0", () => { + const result = setFontSettingsSchema.safeParse({ + editorFontSize: 0, + }); + expect(result.success).toBe(false); + }); + + it("rejects font size above maximum (> 24)", () => { + const result = setFontSettingsSchema.safeParse({ + terminalFontSize: 25, + }); + expect(result.success).toBe(false); + }); + + it("rejects very large font size", () => { + const result = setFontSettingsSchema.safeParse({ + editorFontSize: 100, + }); + expect(result.success).toBe(false); + }); + + it("rejects non-integer font sizes", () => { + const result = setFontSettingsSchema.safeParse({ + terminalFontSize: 14.5, + }); + expect(result.success).toBe(false); + }); + + it("accepts null font size (reset)", () => { + const result = setFontSettingsSchema.safeParse({ + terminalFontSize: null, + editorFontSize: null, + }); + expect(result.success).toBe(true); + }); + }); + + describe("font family trimming", () => { + it("trims whitespace from font family", () => { + const input = setFontSettingsSchema.parse({ + terminalFontFamily: " JetBrains Mono ", + }); + const result = transformFontSettings(input); + expect(result.terminalFontFamily).toBe("JetBrains Mono"); + }); + + it("trims whitespace from editor font family", () => { + const input = setFontSettingsSchema.parse({ + editorFontFamily: " Fira Code ", + }); + const result = transformFontSettings(input); + expect(result.editorFontFamily).toBe("Fira Code"); + }); + + it("accepts valid font families without modification", () => { + const input = setFontSettingsSchema.parse({ + terminalFontFamily: "JetBrains Mono", + editorFontFamily: "Fira Code", + }); + const result = transformFontSettings(input); + expect(result.terminalFontFamily).toBe("JetBrains Mono"); + expect(result.editorFontFamily).toBe("Fira Code"); + }); + + it("accepts common monospace fonts", () => { + const fonts = [ + "JetBrains Mono", + "Fira Code", + "Source Code Pro", + "Cascadia Code", + "IBM Plex Mono", + "Hack", + "Inconsolata", + ]; + + for (const font of fonts) { + const input = setFontSettingsSchema.parse({ + terminalFontFamily: font, + }); + const result = transformFontSettings(input); + expect(result.terminalFontFamily).toBe(font); + } + }); + }); + + describe("empty string as null (reset)", () => { + it("treats empty string font family as null", () => { + const input = setFontSettingsSchema.parse({ + terminalFontFamily: "", + }); + const result = transformFontSettings(input); + expect(result.terminalFontFamily).toBeNull(); + }); + + it("treats whitespace-only font family as null", () => { + const input = setFontSettingsSchema.parse({ + editorFontFamily: " ", + }); + const result = transformFontSettings(input); + expect(result.editorFontFamily).toBeNull(); + }); + + it("treats null font family as null", () => { + const input = setFontSettingsSchema.parse({ + terminalFontFamily: null, + }); + const result = transformFontSettings(input); + expect(result.terminalFontFamily).toBeNull(); + }); + }); + + describe("partial updates", () => { + it("only includes provided fields in the result", () => { + const input = setFontSettingsSchema.parse({ + terminalFontFamily: "Fira Code", + }); + const result = transformFontSettings(input); + expect(result.terminalFontFamily).toBe("Fira Code"); + expect(result).not.toHaveProperty("editorFontFamily"); + expect(result).not.toHaveProperty("terminalFontSize"); + expect(result).not.toHaveProperty("editorFontSize"); + }); + + it("accepts empty input (no changes)", () => { + const input = setFontSettingsSchema.parse({}); + const result = transformFontSettings(input); + expect(Object.keys(result)).toHaveLength(0); + }); + }); +}); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts new file mode 100644 index 00000000000..44c00b87f5a --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts @@ -0,0 +1,65 @@ +import { describe, expect, it } from "bun:test"; +import { + SETTING_ITEM_ID, + searchSettings, + type SettingsItem, +} from "./settings-search"; + +function getIds(items: SettingsItem[]): string[] { + return items.map((item) => item.id); +} + +describe("settings search - font settings", () => { + it('searching "font" returns both APPEARANCE_EDITOR_FONT and APPEARANCE_TERMINAL_FONT', () => { + const results = searchSettings("font"); + const ids = getIds(results); + expect(ids).toContain(SETTING_ITEM_ID.APPEARANCE_EDITOR_FONT); + expect(ids).toContain(SETTING_ITEM_ID.APPEARANCE_TERMINAL_FONT); + }); + + it('searching "terminal font" returns APPEARANCE_TERMINAL_FONT', () => { + const results = searchSettings("terminal font"); + const ids = getIds(results); + expect(ids).toContain(SETTING_ITEM_ID.APPEARANCE_TERMINAL_FONT); + }); + + it('searching "editor" returns APPEARANCE_EDITOR_FONT', () => { + const results = searchSettings("editor"); + const ids = getIds(results); + expect(ids).toContain(SETTING_ITEM_ID.APPEARANCE_EDITOR_FONT); + }); + + it('searching "monospace" returns both font items', () => { + const results = searchSettings("monospace"); + const ids = getIds(results); + expect(ids).toContain(SETTING_ITEM_ID.APPEARANCE_EDITOR_FONT); + expect(ids).toContain(SETTING_ITEM_ID.APPEARANCE_TERMINAL_FONT); + }); + + it('searching "Editor Font" is case-insensitive', () => { + const results = searchSettings("Editor Font"); + const ids = getIds(results); + expect(ids).toContain(SETTING_ITEM_ID.APPEARANCE_EDITOR_FONT); + }); + + it("empty search returns all settings items", () => { + const results = searchSettings(""); + expect(results.length).toBeGreaterThan(0); + const ids = getIds(results); + expect(ids).toContain(SETTING_ITEM_ID.APPEARANCE_EDITOR_FONT); + expect(ids).toContain(SETTING_ITEM_ID.APPEARANCE_TERMINAL_FONT); + }); + + it("font items have correct section", () => { + const results = searchSettings("font"); + const editorFont = results.find( + (r) => r.id === SETTING_ITEM_ID.APPEARANCE_EDITOR_FONT, + ); + const terminalFont = results.find( + (r) => r.id === SETTING_ITEM_ID.APPEARANCE_TERMINAL_FONT, + ); + + expect(editorFont?.section).toBe("appearance"); + expect(terminalFont?.section).toBe("appearance"); + }); +}); From ab02b4c2b6e0689fbdcec7f6d19e4e7f385e8cd0 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 11:46:28 +0300 Subject: [PATCH 6/8] fix(appearance): address CodeRabbit review feedback on font settings --- .../MonacoProvider/MonacoProvider.tsx | 5 ++++- .../AppearanceSettings/AppearanceSettings.tsx | 19 +++++++++++-------- .../settings-search/settings-search.test.ts | 2 +- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/apps/desktop/src/renderer/providers/MonacoProvider/MonacoProvider.tsx b/apps/desktop/src/renderer/providers/MonacoProvider/MonacoProvider.tsx index 65b113c9f46..0876d05f6db 100644 --- a/apps/desktop/src/renderer/providers/MonacoProvider/MonacoProvider.tsx +++ b/apps/desktop/src/renderer/providers/MonacoProvider/MonacoProvider.tsx @@ -138,13 +138,16 @@ export function useMonacoEditorOptions() { return useMemo(() => { if (!fontSettings) return MONACO_EDITOR_OPTIONS; + const fontSize = + fontSettings.editorFontSize ?? MONACO_EDITOR_OPTIONS.fontSize; return { ...MONACO_EDITOR_OPTIONS, ...(fontSettings.editorFontFamily && { fontFamily: fontSettings.editorFontFamily, }), ...(fontSettings.editorFontSize != null && { - fontSize: fontSettings.editorFontSize, + fontSize, + lineHeight: Math.round(fontSize * 1.5), }), }; }, [fontSettings]); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx index 17539b8a150..03e27f9b388 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx @@ -9,6 +9,11 @@ import { } from "@superset/ui/select"; import { useCallback, useState } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; +import { MONACO_EDITOR_OPTIONS } from "renderer/providers/MonacoProvider"; +import { + DEFAULT_TERMINAL_FONT_FAMILY, + DEFAULT_TERMINAL_FONT_SIZE, +} from "renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/config"; import { type MarkdownStyle, SYSTEM_THEME_ID, @@ -27,12 +32,8 @@ import { import { SystemThemeCard } from "./components/SystemThemeCard"; import { ThemeCard } from "./components/ThemeCard"; -const DEFAULT_EDITOR_FONT_FAMILY = - "ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, Liberation Mono, monospace"; -const DEFAULT_EDITOR_FONT_SIZE = 13; -const DEFAULT_TERMINAL_FONT_FAMILY = - "MesloLGM Nerd Font, MesloLGM NF, Menlo, Monaco, monospace"; -const DEFAULT_TERMINAL_FONT_SIZE = 14; +const DEFAULT_EDITOR_FONT_FAMILY = MONACO_EDITOR_OPTIONS.fontFamily; +const DEFAULT_EDITOR_FONT_SIZE = MONACO_EDITOR_OPTIONS.fontSize; const FONT_PREVIEW_TEXT = "The quick brown fox jumps over the lazy dog.\n0O1lI {}[]() => !== +- @#$%"; @@ -253,7 +254,7 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) {
setEditorFontDraft(e.target.value)} onBlur={(e) => { handleEditorFontFamilyBlur(e); @@ -321,7 +322,9 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) {
setTerminalFontDraft(e.target.value)} onBlur={(e) => { handleTerminalFontFamilyBlur(e); diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts index 44c00b87f5a..5b860fb8946 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.test.ts @@ -1,8 +1,8 @@ import { describe, expect, it } from "bun:test"; import { SETTING_ITEM_ID, - searchSettings, type SettingsItem, + searchSettings, } from "./settings-search"; function getIds(items: SettingsItem[]): string[] { From 0665f013a98c73c75473d90294501409d4036336 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 13:20:30 +0300 Subject: [PATCH 7/8] refactor(settings): extract font settings schema into shared module - Move Zod schema and transform logic to font-settings.utils.ts (single source of truth) and import in both the router and test file - Add .max(500) length limit on font family strings for safety - Add no-op guard to skip DB write when no fields are provided - Remove ineffective defaults test that only asserted its own literals Addresses remaining CodeRabbit review feedback on PR #1336. Co-Authored-By: Claude Opus 4.6 --- .../routers/settings/font-settings.test.ts | 60 ++----------------- .../routers/settings/font-settings.utils.ts | 31 ++++++++++ .../src/lib/trpc/routers/settings/index.ts | 40 +++---------- 3 files changed, 43 insertions(+), 88 deletions(-) create mode 100644 apps/desktop/src/lib/trpc/routers/settings/font-settings.utils.ts diff --git a/apps/desktop/src/lib/trpc/routers/settings/font-settings.test.ts b/apps/desktop/src/lib/trpc/routers/settings/font-settings.test.ts index 086e29f6803..2e843889141 100644 --- a/apps/desktop/src/lib/trpc/routers/settings/font-settings.test.ts +++ b/apps/desktop/src/lib/trpc/routers/settings/font-settings.test.ts @@ -1,62 +1,10 @@ import { describe, expect, it } from "bun:test"; -import { z } from "zod"; - -/** - * Font settings validation tests. - * - * These test the Zod validation schema and the transformation logic used - * by the setFontSettings tRPC procedure, extracted here for unit testing - * without requiring a database connection. - */ - -// Replicate the exact Zod schema from the router -const setFontSettingsSchema = z.object({ - terminalFontFamily: z.string().nullable().optional(), - terminalFontSize: z.number().int().min(10).max(24).nullable().optional(), - editorFontFamily: z.string().nullable().optional(), - editorFontSize: z.number().int().min(10).max(24).nullable().optional(), -}); - -// Replicate the transformation logic from the mutation -function transformFontSettings( - input: z.infer, -): Record { - const set: Record = {}; - - if (input.terminalFontFamily !== undefined) { - set.terminalFontFamily = input.terminalFontFamily?.trim() || null; - } - if (input.terminalFontSize !== undefined) { - set.terminalFontSize = input.terminalFontSize; - } - if (input.editorFontFamily !== undefined) { - set.editorFontFamily = input.editorFontFamily?.trim() || null; - } - if (input.editorFontSize !== undefined) { - set.editorFontSize = input.editorFontSize; - } - - return set; -} +import { + setFontSettingsSchema, + transformFontSettings, +} from "./font-settings.utils"; describe("font settings validation", () => { - describe("getFontSettings defaults", () => { - it("returns all null when no settings are stored", () => { - // Simulates the default return shape of getFontSettings - const defaults = { - terminalFontFamily: null, - terminalFontSize: null, - editorFontFamily: null, - editorFontSize: null, - }; - - expect(defaults.terminalFontFamily).toBeNull(); - expect(defaults.terminalFontSize).toBeNull(); - expect(defaults.editorFontFamily).toBeNull(); - expect(defaults.editorFontSize).toBeNull(); - }); - }); - describe("font size validation (range 10-24)", () => { it("accepts font size at minimum (10)", () => { const result = setFontSettingsSchema.safeParse({ diff --git a/apps/desktop/src/lib/trpc/routers/settings/font-settings.utils.ts b/apps/desktop/src/lib/trpc/routers/settings/font-settings.utils.ts new file mode 100644 index 00000000000..0b471592434 --- /dev/null +++ b/apps/desktop/src/lib/trpc/routers/settings/font-settings.utils.ts @@ -0,0 +1,31 @@ +import { z } from "zod"; + +export const setFontSettingsSchema = z.object({ + terminalFontFamily: z.string().max(500).nullable().optional(), + terminalFontSize: z.number().int().min(10).max(24).nullable().optional(), + editorFontFamily: z.string().max(500).nullable().optional(), + editorFontSize: z.number().int().min(10).max(24).nullable().optional(), +}); + +export type SetFontSettingsInput = z.infer; + +export function transformFontSettings( + input: SetFontSettingsInput, +): Record { + const set: Record = {}; + + if (input.terminalFontFamily !== undefined) { + set.terminalFontFamily = input.terminalFontFamily?.trim() || null; + } + if (input.terminalFontSize !== undefined) { + set.terminalFontSize = input.terminalFontSize; + } + if (input.editorFontFamily !== undefined) { + set.editorFontFamily = input.editorFontFamily?.trim() || null; + } + if (input.editorFontSize !== undefined) { + set.editorFontSize = input.editorFontSize; + } + + return set; +} diff --git a/apps/desktop/src/lib/trpc/routers/settings/index.ts b/apps/desktop/src/lib/trpc/routers/settings/index.ts index 1d61b8c6316..a4562856f33 100644 --- a/apps/desktop/src/lib/trpc/routers/settings/index.ts +++ b/apps/desktop/src/lib/trpc/routers/settings/index.ts @@ -20,6 +20,10 @@ import { DEFAULT_RINGTONE_ID, RINGTONES } from "shared/ringtones"; import { z } from "zod"; import { publicProcedure, router } from "../.."; import { getGitAuthorName, getGitHubUsername } from "../workspaces/utils/git"; +import { + setFontSettingsSchema, + transformFontSettings, +} from "./font-settings.utils"; const VALID_RINGTONE_IDS = RINGTONES.map((r) => r.id); @@ -506,40 +510,12 @@ export const createSettingsRouter = () => { }), setFontSettings: publicProcedure - .input( - z.object({ - terminalFontFamily: z.string().nullable().optional(), - terminalFontSize: z - .number() - .int() - .min(10) - .max(24) - .nullable() - .optional(), - editorFontFamily: z.string().nullable().optional(), - editorFontSize: z - .number() - .int() - .min(10) - .max(24) - .nullable() - .optional(), - }), - ) + .input(setFontSettingsSchema) .mutation(({ input }) => { - const set: Record = {}; + const set = transformFontSettings(input); - if (input.terminalFontFamily !== undefined) { - set.terminalFontFamily = input.terminalFontFamily?.trim() || null; - } - if (input.terminalFontSize !== undefined) { - set.terminalFontSize = input.terminalFontSize; - } - if (input.editorFontFamily !== undefined) { - set.editorFontFamily = input.editorFontFamily?.trim() || null; - } - if (input.editorFontSize !== undefined) { - set.editorFontSize = input.editorFontSize; + if (Object.keys(set).length === 0) { + return { success: true }; } localDb From 494642d2c224ff6432655058c3b41f55a1f73b40 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Wed, 11 Feb 2026 01:09:30 +0300 Subject: [PATCH 8/8] fix: address CodeRabbit review feedback on font settings - Add draft state for font size inputs (editor & terminal) so mutations only fire on blur instead of every keystroke, matching the existing font family draft pattern - Replace fake snapshot UUID in migration 0021 with a real UUID v4 --- .../AppearanceSettings/AppearanceSettings.tsx | 61 +++++++++++++++---- .../drizzle/0022_add_font_settings.sql | 2 +- .../local-db/drizzle/meta/0022_snapshot.json | 2 +- 3 files changed, 52 insertions(+), 13 deletions(-) diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx index 03e27f9b388..f566bbc2826 100644 --- a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/AppearanceSettings.tsx @@ -7,7 +7,7 @@ import { SelectTrigger, SelectValue, } from "@superset/ui/select"; -import { useCallback, useState } from "react"; +import { useCallback, useEffect, useState } from "react"; import { electronTrpc } from "renderer/lib/electron-trpc"; import { MONACO_EDITOR_OPTIONS } from "renderer/providers/MonacoProvider"; import { @@ -137,8 +137,8 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { [setFontSettings], ); - const handleEditorFontSizeChange = useCallback( - (e: React.ChangeEvent) => { + const handleEditorFontSizeBlur = useCallback( + (e: React.FocusEvent) => { const value = Number.parseInt(e.target.value, 10); if (!Number.isNaN(value) && value >= 10 && value <= 24) { setFontSettings.mutate({ editorFontSize: value }); @@ -157,8 +157,8 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { [setFontSettings], ); - const handleTerminalFontSizeChange = useCallback( - (e: React.ChangeEvent) => { + const handleTerminalFontSizeBlur = useCallback( + (e: React.FocusEvent) => { const value = Number.parseInt(e.target.value, 10); if (!Number.isNaN(value) && value >= 10 && value <= 24) { setFontSettings.mutate({ terminalFontSize: value }); @@ -171,19 +171,39 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { const [terminalFontDraft, setTerminalFontDraft] = useState( null, ); + const [editorFontSizeDraft, setEditorFontSizeDraft] = useState( + null, + ); + const [terminalFontSizeDraft, setTerminalFontSizeDraft] = useState< + string | null + >(null); + + // biome-ignore lint/correctness/useExhaustiveDependencies: sync draft state when fontSettings changes + useEffect(() => { + setEditorFontSizeDraft(null); + setTerminalFontSizeDraft(null); + }, [fontSettings]); const editorPreviewFamily = editorFontDraft ?? fontSettings?.editorFontFamily ?? DEFAULT_EDITOR_FONT_FAMILY; const editorPreviewSize = - fontSettings?.editorFontSize ?? DEFAULT_EDITOR_FONT_SIZE; + (editorFontSizeDraft != null + ? Number.parseInt(editorFontSizeDraft, 10) + : undefined) || + fontSettings?.editorFontSize || + DEFAULT_EDITOR_FONT_SIZE; const terminalPreviewFamily = terminalFontDraft ?? fontSettings?.terminalFontFamily ?? DEFAULT_TERMINAL_FONT_FAMILY; const terminalPreviewSize = - fontSettings?.terminalFontSize ?? DEFAULT_TERMINAL_FONT_SIZE; + (terminalFontSizeDraft != null + ? Number.parseInt(terminalFontSizeDraft, 10) + : undefined) || + fontSettings?.terminalFontSize || + DEFAULT_TERMINAL_FONT_SIZE; const hasPrecedingSection = showTheme || showMarkdown; @@ -267,8 +287,17 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { type="number" min={10} max={24} - value={fontSettings?.editorFontSize ?? DEFAULT_EDITOR_FONT_SIZE} - onChange={handleEditorFontSizeChange} + value={ + editorFontSizeDraft ?? + String( + fontSettings?.editorFontSize ?? DEFAULT_EDITOR_FONT_SIZE, + ) + } + onChange={(e) => setEditorFontSizeDraft(e.target.value)} + onBlur={(e) => { + handleEditorFontSizeBlur(e); + setEditorFontSizeDraft(null); + }} disabled={isFontLoading} className="w-20" /> @@ -284,6 +313,7 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { editorFontSize: null, }); setEditorFontDraft(null); + setEditorFontSizeDraft(null); }} > Reset @@ -338,9 +368,17 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { min={10} max={24} value={ - fontSettings?.terminalFontSize ?? DEFAULT_TERMINAL_FONT_SIZE + terminalFontSizeDraft ?? + String( + fontSettings?.terminalFontSize ?? + DEFAULT_TERMINAL_FONT_SIZE, + ) } - onChange={handleTerminalFontSizeChange} + onChange={(e) => setTerminalFontSizeDraft(e.target.value)} + onBlur={(e) => { + handleTerminalFontSizeBlur(e); + setTerminalFontSizeDraft(null); + }} disabled={isFontLoading} className="w-20" /> @@ -356,6 +394,7 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { terminalFontSize: null, }); setTerminalFontDraft(null); + setTerminalFontSizeDraft(null); }} > Reset diff --git a/packages/local-db/drizzle/0022_add_font_settings.sql b/packages/local-db/drizzle/0022_add_font_settings.sql index 2e0fd82303d..86d3f273cc5 100644 --- a/packages/local-db/drizzle/0022_add_font_settings.sql +++ b/packages/local-db/drizzle/0022_add_font_settings.sql @@ -1,4 +1,4 @@ ALTER TABLE `settings` ADD `terminal_font_family` text;--> statement-breakpoint ALTER TABLE `settings` ADD `terminal_font_size` integer;--> statement-breakpoint ALTER TABLE `settings` ADD `editor_font_family` text;--> statement-breakpoint -ALTER TABLE `settings` ADD `editor_font_size` integer; \ No newline at end of file +ALTER TABLE `settings` ADD `editor_font_size` integer; diff --git a/packages/local-db/drizzle/meta/0022_snapshot.json b/packages/local-db/drizzle/meta/0022_snapshot.json index ac8353d9e8f..21baf7ff615 100644 --- a/packages/local-db/drizzle/meta/0022_snapshot.json +++ b/packages/local-db/drizzle/meta/0022_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "a1b2c3d4-font-sett-ings-snapshot000021", + "id": "6cbb83fb-b7c0-46fa-830a-2c63f67bc147", "prevId": "8a53a71d-3904-41e1-8c3f-b0322d375a78", "tables": { "organization_members": {