From c0d540375006bc176547ba5fabe862d1bc88fb8c Mon Sep 17 00:00:00 2001 From: nchopra Date: Tue, 16 Sep 2025 21:57:24 +0530 Subject: [PATCH] Squashed commit of the following: commit d8c3e580c3026379fe94dc3ee26391e64a027fef Author: Haritabh Singh Date: Tue Sep 16 21:52:24 2025 +0530 fix: make silent true for launched node process commit 8cc95ea4d8b47763ec3018929b8fd6c505d5c506 Author: Haritabh Singh Date: Tue Sep 16 21:45:38 2025 +0530 chore: show traffic light spacing only on macs commit ad4751cea8c3be36a93d327b109d68c50bb9a98b Author: nchopra Date: Tue Sep 16 21:33:41 2025 +0530 feat: windows action buttons theme sync --- apps/desktop/src/components/site-header.tsx | 9 +- apps/desktop/src/components/theme-toggle.tsx | 40 +++---- apps/desktop/src/db/app-settings.ts | 2 - apps/desktop/src/db/schema.ts | 8 -- apps/desktop/src/main/core/window-manager.ts | 100 +++++++++++++++++- .../transcription/simple-fork-wrapper.ts | 2 +- apps/desktop/src/services/settings-service.ts | 8 +- apps/desktop/src/trpc/routers/settings.ts | 36 +++++++ 8 files changed, 161 insertions(+), 44 deletions(-) diff --git a/apps/desktop/src/components/site-header.tsx b/apps/desktop/src/components/site-header.tsx index 270e33b..8bd6702 100644 --- a/apps/desktop/src/components/site-header.tsx +++ b/apps/desktop/src/components/site-header.tsx @@ -17,6 +17,13 @@ export function SiteHeader({ currentView }: SiteHeaderProps) { const router = useRouter(); const [canGoBack, setCanGoBack] = useState(false); const [canGoForward, setCanGoForward] = useState(false); + const [isMacOS, setIsMacOS] = useState(false); + + useEffect(() => { + // Detect if running on macOS + const platform = navigator.platform || navigator.userAgent; + setIsMacOS(/Mac|Darwin/i.test(platform)); + }, []); useEffect(() => { // Track navigation history in session storage @@ -136,7 +143,7 @@ export function SiteHeader({ currentView }: SiteHeaderProps) { >
{/* macOS traffic light button spacing */} -
+ {isMacOS &&
}
diff --git a/apps/desktop/src/components/theme-toggle.tsx b/apps/desktop/src/components/theme-toggle.tsx index f6bd499..6b06a5f 100644 --- a/apps/desktop/src/components/theme-toggle.tsx +++ b/apps/desktop/src/components/theme-toggle.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { Moon, Sun, Monitor } from "lucide-react"; import { useTheme } from "next-themes"; +import { api } from "@/trpc/react"; import { Button } from "@/components/ui/button"; import { @@ -11,7 +12,16 @@ import { } from "@/components/ui/dropdown-menu"; export function ThemeToggle() { - const { setTheme, theme } = useTheme(); + const { setTheme } = useTheme(); + const updateUIThemeMutation = api.settings.updateUITheme.useMutation(); + + const handleThemeChange = (newTheme: "light" | "dark" | "system") => { + // Update local theme immediately for instant feedback + setTheme(newTheme); + + // Sync with backend + updateUIThemeMutation.mutate({ theme: newTheme }); + }; return ( @@ -23,15 +33,15 @@ export function ThemeToggle() { - setTheme("light")}> + handleThemeChange("light")}> Light - setTheme("dark")}> + handleThemeChange("dark")}> Dark - setTheme("system")}> + handleThemeChange("system")}> System @@ -39,25 +49,3 @@ export function ThemeToggle() { ); } - -export function ThemeToggleSimple() { - const { setTheme, theme } = useTheme(); - - const toggleTheme = () => { - if (theme === "light") { - setTheme("dark"); - } else if (theme === "dark") { - setTheme("system"); - } else { - setTheme("light"); - } - }; - - return ( - - ); -} diff --git a/apps/desktop/src/db/app-settings.ts b/apps/desktop/src/db/app-settings.ts index 70cc9ee..0441ffc 100644 --- a/apps/desktop/src/db/app-settings.ts +++ b/apps/desktop/src/db/app-settings.ts @@ -48,8 +48,6 @@ const defaultSettings: AppSettingsData = { }, ui: { theme: "system", - sidebarOpen: false, - currentView: "Voice Recording", }, transcription: { language: "en", diff --git a/apps/desktop/src/db/schema.ts b/apps/desktop/src/db/schema.ts index f9fa3f4..9511cc3 100644 --- a/apps/desktop/src/db/schema.ts +++ b/apps/desktop/src/db/schema.ts @@ -115,14 +115,6 @@ export interface AppSettingsData { }; ui?: { theme: "light" | "dark" | "system"; - sidebarOpen?: boolean; - currentView?: string; - windowBounds?: { - x: number; - y: number; - width: number; - height: number; - }; }; transcription?: { language: string; diff --git a/apps/desktop/src/main/core/window-manager.ts b/apps/desktop/src/main/core/window-manager.ts index 2a3a0a2..fabe3f9 100644 --- a/apps/desktop/src/main/core/window-manager.ts +++ b/apps/desktop/src/main/core/window-manager.ts @@ -1,4 +1,10 @@ -import { BrowserWindow, screen, systemPreferences, app } from "electron"; +import { + BrowserWindow, + screen, + systemPreferences, + app, + nativeTheme, +} from "electron"; import path from "node:path"; import { logger } from "../logger"; import { ServiceManager } from "../managers/service-manager"; @@ -16,22 +22,105 @@ export class WindowManager { private onboardingWindow: BrowserWindow | null = null; private widgetDisplayId: number | null = null; private cursorPollingInterval: NodeJS.Timeout | null = null; + private themeListenerSetup: boolean = false; - createOrShowMainWindow(): void { + private async getThemeColors(): Promise<{ + backgroundColor: string; + symbolColor: string; + }> { + try { + const settingsService = + ServiceManager.getInstance()?.getService("settingsService"); + if (!settingsService) { + // Default to light theme if service unavailable + return { backgroundColor: "#ffffff", symbolColor: "#000000" }; + } + + const uiSettings = await settingsService.getUISettings(); + const theme = uiSettings?.theme || "system"; + + // Determine if we should use dark colors + let isDark = false; + if (theme === "dark") { + isDark = true; + } else if (theme === "light") { + isDark = false; + } else if (theme === "system") { + isDark = nativeTheme.shouldUseDarkColors; + } + + // Return appropriate colors + return isDark + ? { backgroundColor: "#171717", symbolColor: "#fafafa" } + : { backgroundColor: "#ffffff", symbolColor: "#171717" }; + } catch (error) { + logger.main.error("Failed to get theme colors:", error); + // Default to light theme on error + return { backgroundColor: "#ffffff", symbolColor: "#000000" }; + } + } + + async updateAllWindowThemes(): Promise { + const colors = await this.getThemeColors(); + + // Update main window + if (this.mainWindow && !this.mainWindow.isDestroyed()) { + this.mainWindow.setTitleBarOverlay({ + color: colors.backgroundColor, + symbolColor: colors.symbolColor, + height: 32, + }); + } + + // Update onboarding window if it exists + // Note: onboarding window has frame: false, so no title bar to update + + logger.main.info("Updated window themes", colors); + } + + private setupThemeListener(): void { + if (this.themeListenerSetup) return; + + // Listen for system theme changes + nativeTheme.on("updated", async () => { + const settingsService = + ServiceManager.getInstance()!.getService("settingsService")!; + + const uiSettings = await settingsService.getUISettings(); + const theme = uiSettings?.theme || "system"; + + // Only update if theme is set to "system" + if (theme === "system") { + await this.updateAllWindowThemes(); + logger.main.info("System theme changed, updating windows"); + } + }); + + this.themeListenerSetup = true; + logger.main.info("Theme listener setup complete"); + } + + async createOrShowMainWindow(): Promise { if (this.mainWindow && !this.mainWindow.isDestroyed()) { this.mainWindow.show(); this.mainWindow.focus(); return; } + // Setup theme listener on first window creation + this.setupThemeListener(); + + // Get theme colors before creating window + const colors = await this.getThemeColors(); + this.mainWindow = new BrowserWindow({ width: 1200, height: 800, frame: true, titleBarStyle: "hidden", titleBarOverlay: { - color: "#ffffff", - symbolColor: "#000000", + color: colors.backgroundColor, + symbolColor: colors.symbolColor, height: 32, }, trafficLightPosition: { x: 20, y: 16 }, @@ -170,6 +259,9 @@ export class WindowManager { return; } + // Setup theme listener if not already done + this.setupThemeListener(); + this.onboardingWindow = new BrowserWindow({ width: 700, height: 600, diff --git a/apps/desktop/src/pipeline/providers/transcription/simple-fork-wrapper.ts b/apps/desktop/src/pipeline/providers/transcription/simple-fork-wrapper.ts index 236e1dc..750502d 100644 --- a/apps/desktop/src/pipeline/providers/transcription/simple-fork-wrapper.ts +++ b/apps/desktop/src/pipeline/providers/transcription/simple-fork-wrapper.ts @@ -61,7 +61,7 @@ export class SimpleForkWrapper { this.worker = fork(actualWorkerPath, [], { execPath: this.nodeBinaryPath, env: workerEnv, - silent: false, + silent: true, cwd: app.isPackaged ? process.resourcesPath : process.cwd(), }); diff --git a/apps/desktop/src/services/settings-service.ts b/apps/desktop/src/services/settings-service.ts index 4525267..7a6e359 100644 --- a/apps/desktop/src/services/settings-service.ts +++ b/apps/desktop/src/services/settings-service.ts @@ -7,7 +7,7 @@ import { updateAppSettings, } from "../db/app-settings"; import type { AppSettingsData } from "../db/schema"; -import { isWindows, isMacOS } from "../utils/platform"; +import { isMacOS } from "../utils/platform"; /** * Database-backed settings service with typed configuration @@ -61,7 +61,11 @@ export class SettingsService { * Get UI settings */ async getUISettings(): Promise { - return await getSettingsSection("ui"); + return ( + (await getSettingsSection("ui")) ?? { + theme: "system", + } + ); } /** diff --git a/apps/desktop/src/trpc/routers/settings.ts b/apps/desktop/src/trpc/routers/settings.ts index 8aaf500..e371d2e 100644 --- a/apps/desktop/src/trpc/routers/settings.ts +++ b/apps/desktop/src/trpc/routers/settings.ts @@ -42,6 +42,10 @@ const AppPreferencesSchema = z.object({ showWidgetWhileInactive: z.boolean().optional(), }); +const UIThemeSchema = z.object({ + theme: z.enum(["light", "dark", "system"]), +}); + export const settingsRouter = createRouter({ // Get all settings getSettings: procedure.query(async ({ ctx }) => { @@ -549,6 +553,38 @@ export const settingsRouter = createRouter({ return true; }), + // Update UI theme + updateUITheme: procedure + .input(UIThemeSchema) + .mutation(async ({ input, ctx }) => { + const settingsService = ctx.serviceManager.getService("settingsService"); + if (!settingsService) { + throw new Error("SettingsService not available"); + } + + // Get current UI settings + const currentUISettings = await settingsService.getUISettings(); + + // Update with new theme + await settingsService.setUISettings({ + ...currentUISettings, + theme: input.theme, + }); + + // Update all window themes immediately + const windowManager = ctx.serviceManager.getService("windowManager"); + if (windowManager) { + await windowManager.updateAllWindowThemes(); + } + + const logger = ctx.serviceManager.getLogger(); + if (logger) { + logger.main.info("UI theme updated", { theme: input.theme }); + } + + return true; + }), + // Reset app - deletes all data and restarts resetApp: procedure.mutation(async ({ ctx }) => { try {