Squashed commit of the following:

commit d8c3e580c3026379fe94dc3ee26391e64a027fef
Author: Haritabh Singh <haritabh.z01+github@gmail.com>
Date:   Tue Sep 16 21:52:24 2025 +0530

    fix: make silent true for launched node process

commit 8cc95ea4d8b47763ec3018929b8fd6c505d5c506
Author: Haritabh Singh <haritabh.z01+github@gmail.com>
Date:   Tue Sep 16 21:45:38 2025 +0530

    chore: show traffic light spacing only on macs

commit ad4751cea8c3be36a93d327b109d68c50bb9a98b
Author: nchopra <naomi.chopra.021@gmail.com>
Date:   Tue Sep 16 21:33:41 2025 +0530

    feat: windows action buttons theme sync
This commit is contained in:
nchopra 2025-09-16 21:57:24 +05:30
parent c38d9eab1c
commit c0d5403750
8 changed files with 161 additions and 44 deletions

View file

@ -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) {
>
<div className="flex w-full items-center gap-1">
{/* macOS traffic light button spacing */}
<div className="w-[78px] flex-shrink-0" />
{isMacOS && <div className="w-[78px] flex-shrink-0" />}
<div className="flex items-center gap-1 px-4 lg:gap-2 lg:px-6 py-1.5">
<SidebarTrigger className="-ml-1" style={noDragRegion} />

View file

@ -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 (
<DropdownMenu>
@ -23,15 +33,15 @@ export function ThemeToggle() {
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setTheme("light")}>
<DropdownMenuItem onClick={() => handleThemeChange("light")}>
<Sun className="mr-2 h-4 w-4" />
<span>Light</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("dark")}>
<DropdownMenuItem onClick={() => handleThemeChange("dark")}>
<Moon className="mr-2 h-4 w-4" />
<span>Dark</span>
</DropdownMenuItem>
<DropdownMenuItem onClick={() => setTheme("system")}>
<DropdownMenuItem onClick={() => handleThemeChange("system")}>
<Monitor className="mr-2 h-4 w-4" />
<span>System</span>
</DropdownMenuItem>
@ -39,25 +49,3 @@ export function ThemeToggle() {
</DropdownMenu>
);
}
export function ThemeToggleSimple() {
const { setTheme, theme } = useTheme();
const toggleTheme = () => {
if (theme === "light") {
setTheme("dark");
} else if (theme === "dark") {
setTheme("system");
} else {
setTheme("light");
}
};
return (
<Button variant="outline" size="icon" onClick={toggleTheme}>
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
<span className="sr-only">Toggle theme</span>
</Button>
);
}

View file

@ -48,8 +48,6 @@ const defaultSettings: AppSettingsData = {
},
ui: {
theme: "system",
sidebarOpen: false,
currentView: "Voice Recording",
},
transcription: {
language: "en",

View file

@ -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;

View file

@ -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<void> {
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<void> {
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,

View file

@ -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(),
});

View file

@ -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<AppSettingsData["ui"]> {
return await getSettingsSection("ui");
return (
(await getSettingsSection("ui")) ?? {
theme: "system",
}
);
}
/**

View file

@ -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 {