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 {