From d76ee7cb89ad7715606052bda8bb396af7df682d Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 11:07:54 +0300 Subject: [PATCH 01/11] 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/0021_add_font_settings.sql | 4 + .../local-db/drizzle/meta/0021_snapshot.json | 1106 +++++++++++++++++ packages/local-db/drizzle/meta/_journal.json | 7 + packages/local-db/src/schema/schema.ts | 4 + 13 files changed, 1492 insertions(+), 9 deletions(-) create mode 100644 packages/local-db/drizzle/0021_add_font_settings.sql create mode 100644 packages/local-db/drizzle/meta/0021_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/0021_add_font_settings.sql b/packages/local-db/drizzle/0021_add_font_settings.sql new file mode 100644 index 00000000000..2e0fd82303d --- /dev/null +++ b/packages/local-db/drizzle/0021_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/0021_snapshot.json b/packages/local-db/drizzle/meta/0021_snapshot.json new file mode 100644 index 00000000000..6b8a421ea10 --- /dev/null +++ b/packages/local-db/drizzle/meta/0021_snapshot.json @@ -0,0 +1,1106 @@ +{ + "version": "6", + "dialect": "sqlite", + "id": "a1b2c3d4-font-sett-ings-snapshot000021", + "prevId": "3ff0af35-7fc1-4e36-8012-b113e7a91372", + "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 + } + }, + "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": {} + } +} \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/_journal.json b/packages/local-db/drizzle/meta/_journal.json index 60394526996..e169ae8c5f6 100644 --- a/packages/local-db/drizzle/meta/_journal.json +++ b/packages/local-db/drizzle/meta/_journal.json @@ -148,6 +148,13 @@ "when": 1770754481921, "tag": "0020_add_file_open_mode_setting", "breakpoints": true + }, + { + "idx": 21, + "version": "6", + "when": 1770823982765, + "tag": "0021_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 8ca558f826c..a76aadc9dd9 100644 --- a/packages/local-db/src/schema/schema.ts +++ b/packages/local-db/src/schema/schema.ts @@ -159,6 +159,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 ef571c8f923f9e1eaa1a4338ce7fe8cb91033a61 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 11:24:59 +0300 Subject: [PATCH 02/11] 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 b83d278355bfe35b333cd95a2d2c75e2f3bb82b1 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 11:28:27 +0300 Subject: [PATCH 03/11] 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 04/11] 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 c6f158493dc8e8c28f6818038afdea0461bbe91a Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 11:42:29 +0300 Subject: [PATCH 05/11] 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 0114b20fc1369d23f5d60796cda1a3db2c35f7d9 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 11:46:28 +0300 Subject: [PATCH 06/11] 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 5307d1001299d8e3582a2439908fe6c24f33a4aa Mon Sep 17 00:00:00 2001 From: glitch418x Date: Mon, 9 Feb 2026 13:20:30 +0300 Subject: [PATCH 07/11] 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 7028cb1bbfdc53c209fb628323ace2a0f5c0f5a6 Mon Sep 17 00:00:00 2001 From: glitch418x Date: Wed, 11 Feb 2026 01:09:30 +0300 Subject: [PATCH 08/11] 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 +++++++++++++++---- .../local-db/drizzle/meta/0021_snapshot.json | 2 +- 2 files changed, 51 insertions(+), 12 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/meta/0021_snapshot.json b/packages/local-db/drizzle/meta/0021_snapshot.json index 6b8a421ea10..b022a45f5ac 100644 --- a/packages/local-db/drizzle/meta/0021_snapshot.json +++ b/packages/local-db/drizzle/meta/0021_snapshot.json @@ -1,7 +1,7 @@ { "version": "6", "dialect": "sqlite", - "id": "a1b2c3d4-font-sett-ings-snapshot000021", + "id": "6cbb83fb-b7c0-46fa-830a-2c63f67bc147", "prevId": "3ff0af35-7fc1-4e36-8012-b113e7a91372", "tables": { "organization_members": { From 5b5f8a10ddf9346a189811462fc7167dd44d6c04 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 11 Feb 2026 12:23:59 -0800 Subject: [PATCH 09/11] fix: regenerate font settings migration after merge with main --- packages/local-db/drizzle/meta/0022_snapshot.json | 11 +++++++++-- packages/local-db/drizzle/meta/_journal.json | 2 +- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/local-db/drizzle/meta/0022_snapshot.json b/packages/local-db/drizzle/meta/0022_snapshot.json index 843ff3489d6..1906c2e3a89 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": "6cbb83fb-b7c0-46fa-830a-2c63f67bc147", + "id": "9b4b835d-bade-4c2d-a569-042165d45105", "prevId": "8a53a71d-3904-41e1-8c3f-b0322d375a78", "tables": { "organization_members": { @@ -279,6 +279,13 @@ "primaryKey": false, "notNull": false, "autoincrement": false + }, + "icon_url": { + "name": "icon_url", + "type": "text", + "primaryKey": false, + "notNull": false, + "autoincrement": false } }, "indexes": { @@ -1103,4 +1110,4 @@ "internal": { "indexes": {} } -} +} \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/_journal.json b/packages/local-db/drizzle/meta/_journal.json index 008df8fa5f0..cc1aecd1f07 100644 --- a/packages/local-db/drizzle/meta/_journal.json +++ b/packages/local-db/drizzle/meta/_journal.json @@ -159,7 +159,7 @@ { "idx": 22, "version": "6", - "when": 1770823982765, + "when": 1770837585251, "tag": "0022_add_font_settings", "breakpoints": true } From 842d3ba6920360c098c9830d7a943b1881bfa6a2 Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 11 Feb 2026 23:30:40 -0800 Subject: [PATCH 10/11] chore: remove font settings migration for regeneration --- .../drizzle/0022_add_font_settings.sql | 4 - .../local-db/drizzle/meta/0022_snapshot.json | 1113 ----------------- packages/local-db/drizzle/meta/_journal.json | 7 - 3 files changed, 1124 deletions(-) delete mode 100644 packages/local-db/drizzle/0022_add_font_settings.sql delete mode 100644 packages/local-db/drizzle/meta/0022_snapshot.json diff --git a/packages/local-db/drizzle/0022_add_font_settings.sql b/packages/local-db/drizzle/0022_add_font_settings.sql deleted file mode 100644 index 2e0fd82303d..00000000000 --- a/packages/local-db/drizzle/0022_add_font_settings.sql +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 1906c2e3a89..00000000000 --- a/packages/local-db/drizzle/meta/0022_snapshot.json +++ /dev/null @@ -1,1113 +0,0 @@ -{ - "version": "6", - "dialect": "sqlite", - "id": "9b4b835d-bade-4c2d-a569-042165d45105", - "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": {} - } -} \ No newline at end of file diff --git a/packages/local-db/drizzle/meta/_journal.json b/packages/local-db/drizzle/meta/_journal.json index cc1aecd1f07..b2454c0233f 100644 --- a/packages/local-db/drizzle/meta/_journal.json +++ b/packages/local-db/drizzle/meta/_journal.json @@ -155,13 +155,6 @@ "when": 1770783531923, "tag": "0021_add_image_project", "breakpoints": true - }, - { - "idx": 22, - "version": "6", - "when": 1770837585251, - "tag": "0022_add_font_settings", - "breakpoints": true } ] } \ No newline at end of file From 3f2be27f7553c399f2644eb599757587968b3b9a Mon Sep 17 00:00:00 2001 From: Kiet Ho Date: Wed, 11 Feb 2026 23:40:27 -0800 Subject: [PATCH 11/11] refactor(desktop): extract AppearanceSettings into section components Break the monolithic 432-line AppearanceSettings into focused section components following the established TerminalSettings pattern: - FontPreview: extracted inline preview component - FontSettingSection: reusable editor/terminal font section (deduplicates ~120 lines) - ThemeSection: owns theme store hooks and grid rendering - MarkdownStyleSection: owns markdown store hooks and select dropdown - CustomThemesSection: placeholder for upcoming feature Use SectionList for automatic border separators, replacing fragile conditional className logic. --- .../AppearanceSettings/AppearanceSettings.tsx | 415 ++---------------- .../CustomThemesSection.tsx | 11 + .../components/CustomThemesSection/index.ts | 1 + .../components/FontPreview/FontPreview.tsx | 29 ++ .../components/FontPreview/index.ts | 1 + .../FontSettingSection/FontSettingSection.tsx | 178 ++++++++ .../components/FontSettingSection/index.ts | 1 + .../MarkdownStyleSection.tsx | 42 ++ .../components/MarkdownStyleSection/index.ts | 1 + .../components/ThemeSection/ThemeSection.tsx | 37 ++ .../components/ThemeSection/index.ts | 1 + 11 files changed, 334 insertions(+), 383 deletions(-) create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/CustomThemesSection/CustomThemesSection.tsx create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/CustomThemesSection/index.ts create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontPreview/FontPreview.tsx create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontPreview/index.ts create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontSettingSection/FontSettingSection.tsx create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontSettingSection/index.ts create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/MarkdownStyleSection/MarkdownStyleSection.tsx create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/MarkdownStyleSection/index.ts create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/ThemeSection.tsx create mode 100644 apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/index.ts 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 f566bbc2826..1f4bb36a9db 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,66 +1,31 @@ -import { Button } from "@superset/ui/button"; -import { Input } from "@superset/ui/input"; -import { - Select, - SelectContent, - SelectItem, - SelectTrigger, - SelectValue, -} from "@superset/ui/select"; -import { useCallback, useEffect, 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, - useMarkdownStyle, - useSetMarkdownStyle, - useSetTheme, - useThemeId, - useThemeStore, -} from "renderer/stores"; -import { builtInThemes } from "shared/themes"; +import type { ReactNode } from "react"; import { isItemVisible, SETTING_ITEM_ID, type SettingItemId, } from "../../../utils/settings-search"; -import { SystemThemeCard } from "./components/SystemThemeCard"; -import { ThemeCard } from "./components/ThemeCard"; - -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 {}[]() => !== +- @#$%"; - -function FontPreview({ - fontFamily, - fontSize, - variant, -}: { - fontFamily: string; - fontSize: number; - variant: "editor" | "terminal"; -}) { - const isTerminal = variant === "terminal"; +import { CustomThemesSection } from "./components/CustomThemesSection"; +import { FontSettingSection } from "./components/FontSettingSection"; +import { MarkdownStyleSection } from "./components/MarkdownStyleSection"; +import { ThemeSection } from "./components/ThemeSection"; + +/** + * Renders a list of visible sections with automatic border separators. + * Each section is its own component that owns its data-fetching, + * so query resolutions in one section don't re-render others. + */ +function SectionList({ children }: { children: ReactNode[] }) { + const visibleChildren = children.filter(Boolean); return ( -
- {FONT_PREVIEW_TEXT} +
+ {visibleChildren.map((child, i) => ( +
0 ? "pt-6 border-t mt-6" : ""} + > + {child} +
+ ))}
); } @@ -78,10 +43,6 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { SETTING_ITEM_ID.APPEARANCE_MARKDOWN, visibleItems, ); - const showCustomThemes = isItemVisible( - SETTING_ITEM_ID.APPEARANCE_CUSTOM_THEMES, - visibleItems, - ); const showEditorFont = isItemVisible( SETTING_ITEM_ID.APPEARANCE_EDITOR_FONT, visibleItems, @@ -90,122 +51,10 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) { SETTING_ITEM_ID.APPEARANCE_TERMINAL_FONT, visibleItems, ); - - const activeThemeId = useThemeId(); - const setTheme = useSetTheme(); - const customThemes = useThemeStore((state) => state.customThemes); - const markdownStyle = useMarkdownStyle(); - const setMarkdownStyle = useSetMarkdownStyle(); - - 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 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 }); - } - }, - [setFontSettings], - ); - - const handleTerminalFontFamilyBlur = useCallback( - (e: React.FocusEvent) => { - const value = e.target.value.trim(); - setFontSettings.mutate({ - terminalFontFamily: value || null, - }); - }, - [setFontSettings], - ); - - 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 }); - } - }, - [setFontSettings], - ); - - const [editorFontDraft, setEditorFontDraft] = useState(null); - const [terminalFontDraft, setTerminalFontDraft] = useState( - null, - ); - const [editorFontSizeDraft, setEditorFontSizeDraft] = useState( - null, + const showCustomThemes = isItemVisible( + SETTING_ITEM_ID.APPEARANCE_CUSTOM_THEMES, + visibleItems, ); - 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 = - (editorFontSizeDraft != null - ? Number.parseInt(editorFontSizeDraft, 10) - : undefined) || - fontSettings?.editorFontSize || - DEFAULT_EDITOR_FONT_SIZE; - const terminalPreviewFamily = - terminalFontDraft ?? - fontSettings?.terminalFontFamily ?? - DEFAULT_TERMINAL_FONT_FAMILY; - const terminalPreviewSize = - (terminalFontSizeDraft != null - ? Number.parseInt(terminalFontSizeDraft, 10) - : undefined) || - fontSettings?.terminalFontSize || - DEFAULT_TERMINAL_FONT_SIZE; - - const hasPrecedingSection = showTheme || showMarkdown; return (
@@ -216,217 +65,17 @@ export function AppearanceSettings({ visibleItems }: AppearanceSettingsProps) {

-
- {/* Theme Section */} - {showTheme && ( -
-

Theme

-
- setTheme(SYSTEM_THEME_ID)} - /> - {allThemes.map((theme) => ( - setTheme(theme.id)} - /> - ))} -
-
- )} - - {showMarkdown && ( -
-

Markdown Style

-

- Rendering style for markdown files when viewing rendered content -

- -

- Tufte style uses elegant serif typography inspired by Edward - Tufte's books -

-
- )} - + + {showTheme && } + {showMarkdown && } {showEditorFont && ( -
-

Editor Font

-

- Font used in diff views and file editors -

-
- setEditorFontDraft(e.target.value)} - onBlur={(e) => { - handleEditorFontFamilyBlur(e); - setEditorFontDraft(null); - }} - disabled={isFontLoading} - className="flex-1" - /> - setEditorFontSizeDraft(e.target.value)} - onBlur={(e) => { - handleEditorFontSizeBlur(e); - setEditorFontSizeDraft(null); - }} - disabled={isFontLoading} - className="w-20" - /> - {(fontSettings?.editorFontFamily || - fontSettings?.editorFontSize) && ( - - )} -
-
- -
-
+ )} - {showTerminalFont && ( -
-

Terminal Font

-

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

-
- setTerminalFontDraft(e.target.value)} - onBlur={(e) => { - handleTerminalFontFamilyBlur(e); - setTerminalFontDraft(null); - }} - disabled={isFontLoading} - className="flex-1" - /> - setTerminalFontSizeDraft(e.target.value)} - onBlur={(e) => { - handleTerminalFontSizeBlur(e); - setTerminalFontSizeDraft(null); - }} - disabled={isFontLoading} - className="w-20" - /> - {(fontSettings?.terminalFontFamily || - fontSettings?.terminalFontSize) && ( - - )} -
-
- -
-
- )} - - {showCustomThemes && ( -
-

Custom Themes

-

- Custom theme import coming soon. You'll be able to import JSON - theme files to create your own themes. -

-
+ )} -
+ {showCustomThemes && } +
); } diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/CustomThemesSection/CustomThemesSection.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/CustomThemesSection/CustomThemesSection.tsx new file mode 100644 index 00000000000..9494db8a8ec --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/CustomThemesSection/CustomThemesSection.tsx @@ -0,0 +1,11 @@ +export function CustomThemesSection() { + return ( +
+

Custom Themes

+

+ Custom theme import coming soon. You'll be able to import JSON theme + files to create your own themes. +

+
+ ); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/CustomThemesSection/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/CustomThemesSection/index.ts new file mode 100644 index 00000000000..1343be868d5 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/CustomThemesSection/index.ts @@ -0,0 +1 @@ +export { CustomThemesSection } from "./CustomThemesSection"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontPreview/FontPreview.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontPreview/FontPreview.tsx new file mode 100644 index 00000000000..df7c7b77f60 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontPreview/FontPreview.tsx @@ -0,0 +1,29 @@ +const FONT_PREVIEW_TEXT = + "The quick brown fox jumps over the lazy dog.\n0O1lI {}[]() => !== +- @#$%"; + +export function FontPreview({ + fontFamily, + fontSize, + variant, +}: { + fontFamily: string; + fontSize: number; + variant: "editor" | "terminal"; +}) { + const isTerminal = variant === "terminal"; + return ( +
+ {FONT_PREVIEW_TEXT} +
+ ); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontPreview/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontPreview/index.ts new file mode 100644 index 00000000000..79150bcdefc --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontPreview/index.ts @@ -0,0 +1 @@ +export { FontPreview } from "./FontPreview"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontSettingSection/FontSettingSection.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontSettingSection/FontSettingSection.tsx new file mode 100644 index 00000000000..faefac9f02e --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontSettingSection/FontSettingSection.tsx @@ -0,0 +1,178 @@ +import { Button } from "@superset/ui/button"; +import { Input } from "@superset/ui/input"; +import { useCallback, useEffect, 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 { FontPreview } from "../FontPreview"; + +const DEFAULT_EDITOR_FONT_FAMILY = MONACO_EDITOR_OPTIONS.fontFamily; +const DEFAULT_EDITOR_FONT_SIZE = MONACO_EDITOR_OPTIONS.fontSize; + +const VARIANT_CONFIG = { + editor: { + title: "Editor Font", + description: "Font used in diff views and file editors", + defaultFamily: DEFAULT_EDITOR_FONT_FAMILY, + defaultSize: DEFAULT_EDITOR_FONT_SIZE, + familyKey: "editorFontFamily", + sizeKey: "editorFontSize", + }, + terminal: { + title: "Terminal Font", + description: "Font used in terminal panels.", + defaultFamily: DEFAULT_TERMINAL_FONT_FAMILY, + defaultSize: DEFAULT_TERMINAL_FONT_SIZE, + familyKey: "terminalFontFamily", + sizeKey: "terminalFontSize", + }, +} as const; + +interface FontSettingSectionProps { + variant: "editor" | "terminal"; +} + +export function FontSettingSection({ variant }: FontSettingSectionProps) { + const config = VARIANT_CONFIG[variant]; + + const utils = electronTrpc.useUtils(); + + const { data: fontSettings, isLoading } = + 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 [fontDraft, setFontDraft] = useState(null); + const [fontSizeDraft, setFontSizeDraft] = useState(null); + + // biome-ignore lint/correctness/useExhaustiveDependencies: sync draft state when fontSettings changes + useEffect(() => { + setFontSizeDraft(null); + }, [fontSettings]); + + const currentFamily = fontSettings?.[config.familyKey] ?? null; + const currentSize = fontSettings?.[config.sizeKey] ?? null; + + const handleFontFamilyBlur = useCallback( + (e: React.FocusEvent) => { + const value = e.target.value.trim(); + setFontSettings.mutate({ + [config.familyKey]: value || null, + }); + }, + [setFontSettings, config.familyKey], + ); + + const handleFontSizeBlur = useCallback( + (e: React.FocusEvent) => { + const value = Number.parseInt(e.target.value, 10); + if (!Number.isNaN(value) && value >= 10 && value <= 24) { + setFontSettings.mutate({ [config.sizeKey]: value }); + } + }, + [setFontSettings, config.sizeKey], + ); + + const previewFamily = fontDraft ?? currentFamily ?? config.defaultFamily; + const previewSize = + (fontSizeDraft != null ? Number.parseInt(fontSizeDraft, 10) : undefined) || + currentSize || + config.defaultSize; + + return ( +
+

{config.title}

+

+ {config.description} + {variant === "terminal" && ( + <> + {" "} + + Nerd Fonts + {" "} + recommended for shell theme icons. + + )} +

+
+ setFontDraft(e.target.value)} + onBlur={(e) => { + handleFontFamilyBlur(e); + setFontDraft(null); + }} + disabled={isLoading} + className="flex-1" + /> + setFontSizeDraft(e.target.value)} + onBlur={(e) => { + handleFontSizeBlur(e); + setFontSizeDraft(null); + }} + disabled={isLoading} + className="w-20" + /> + {(currentFamily || currentSize) && ( + + )} +
+
+ +
+
+ ); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontSettingSection/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontSettingSection/index.ts new file mode 100644 index 00000000000..7aaa70e2699 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/FontSettingSection/index.ts @@ -0,0 +1 @@ +export { FontSettingSection } from "./FontSettingSection"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/MarkdownStyleSection/MarkdownStyleSection.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/MarkdownStyleSection/MarkdownStyleSection.tsx new file mode 100644 index 00000000000..7879be7b74d --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/MarkdownStyleSection/MarkdownStyleSection.tsx @@ -0,0 +1,42 @@ +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@superset/ui/select"; +import { + type MarkdownStyle, + useMarkdownStyle, + useSetMarkdownStyle, +} from "renderer/stores"; + +export function MarkdownStyleSection() { + const markdownStyle = useMarkdownStyle(); + const setMarkdownStyle = useSetMarkdownStyle(); + + return ( +
+

Markdown Style

+

+ Rendering style for markdown files when viewing rendered content +

+ +

+ Tufte style uses elegant serif typography inspired by Edward Tufte's + books +

+
+ ); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/MarkdownStyleSection/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/MarkdownStyleSection/index.ts new file mode 100644 index 00000000000..4aa9e23624f --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/MarkdownStyleSection/index.ts @@ -0,0 +1 @@ +export { MarkdownStyleSection } from "./MarkdownStyleSection"; diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/ThemeSection.tsx b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/ThemeSection.tsx new file mode 100644 index 00000000000..16aa2c2148d --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/ThemeSection.tsx @@ -0,0 +1,37 @@ +import { + SYSTEM_THEME_ID, + useSetTheme, + useThemeId, + useThemeStore, +} from "renderer/stores"; +import { builtInThemes } from "shared/themes"; +import { SystemThemeCard } from "../SystemThemeCard"; +import { ThemeCard } from "../ThemeCard"; + +export function ThemeSection() { + const activeThemeId = useThemeId(); + const setTheme = useSetTheme(); + const customThemes = useThemeStore((state) => state.customThemes); + + const allThemes = [...builtInThemes, ...customThemes]; + + return ( +
+

Theme

+
+ setTheme(SYSTEM_THEME_ID)} + /> + {allThemes.map((theme) => ( + setTheme(theme.id)} + /> + ))} +
+
+ ); +} diff --git a/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/index.ts b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/index.ts new file mode 100644 index 00000000000..c8536613cd6 --- /dev/null +++ b/apps/desktop/src/renderer/routes/_authenticated/settings/appearance/components/AppearanceSettings/components/ThemeSection/index.ts @@ -0,0 +1 @@ +export { ThemeSection } from "./ThemeSection";