diff --git a/e2e-tests/auto_update.spec.ts b/e2e-tests/auto_update.spec.ts
new file mode 100644
index 0000000..f03b7ac
--- /dev/null
+++ b/e2e-tests/auto_update.spec.ts
@@ -0,0 +1,15 @@
+import { expect } from "@playwright/test";
+import { test } from "./helpers/test_helper";
+
+test("auto update - disable and enable", async ({ po }) => {
+ await po.goToSettingsTab();
+
+ await po.toggleAutoUpdate();
+ await expect(
+ po.page.getByRole("button", { name: "Restart Dyad" }),
+ ).toBeVisible();
+ await po.snapshotSettings();
+
+ await po.toggleAutoUpdate();
+ await po.snapshotSettings();
+});
diff --git a/e2e-tests/helpers/test_helper.ts b/e2e-tests/helpers/test_helper.ts
index 3fbf366..4453015 100644
--- a/e2e-tests/helpers/test_helper.ts
+++ b/e2e-tests/helpers/test_helper.ts
@@ -833,6 +833,10 @@ export class PageObject {
expect(sanitizedSettingsContent).toMatchSnapshot();
}
+ async toggleAutoUpdate() {
+ await this.page.getByRole("switch", { name: "Auto-update" }).click();
+ }
+
async clickTelemetryAccept() {
await this.page.getByTestId("telemetry-accept-button").click();
}
diff --git a/e2e-tests/snapshots/auto_update.spec.ts_auto-update---disable-and-enable-1.txt b/e2e-tests/snapshots/auto_update.spec.ts_auto-update---disable-and-enable-1.txt
new file mode 100644
index 0000000..da4fa25
--- /dev/null
+++ b/e2e-tests/snapshots/auto_update.spec.ts_auto-update---disable-and-enable-1.txt
@@ -0,0 +1,18 @@
+{
+ "selectedModel": {
+ "name": "auto",
+ "provider": "auto"
+ },
+ "providerSettings": {},
+ "telemetryConsent": "unset",
+ "telemetryUserId": "[UUID]",
+ "hasRunBefore": true,
+ "experiments": {},
+ "lastShownReleaseNotesVersion": "[scrubbed]",
+ "enableProLazyEditsMode": true,
+ "enableProSmartFilesContextMode": true,
+ "selectedChatMode": "build",
+ "enableAutoFixProblems": false,
+ "enableAutoUpdate": false,
+ "isTestMode": true
+}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/auto_update.spec.ts_auto-update---disable-and-enable-2.txt b/e2e-tests/snapshots/auto_update.spec.ts_auto-update---disable-and-enable-2.txt
new file mode 100644
index 0000000..2e1bf3b
--- /dev/null
+++ b/e2e-tests/snapshots/auto_update.spec.ts_auto-update---disable-and-enable-2.txt
@@ -0,0 +1,18 @@
+{
+ "selectedModel": {
+ "name": "auto",
+ "provider": "auto"
+ },
+ "providerSettings": {},
+ "telemetryConsent": "unset",
+ "telemetryUserId": "[UUID]",
+ "hasRunBefore": true,
+ "experiments": {},
+ "lastShownReleaseNotesVersion": "[scrubbed]",
+ "enableProLazyEditsMode": true,
+ "enableProSmartFilesContextMode": true,
+ "selectedChatMode": "build",
+ "enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
+ "isTestMode": true
+}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt b/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt
index 0fa9f0d..fd7eebb 100644
--- a/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt
+++ b/e2e-tests/snapshots/context_window.spec.ts_context-window-4.txt
@@ -15,5 +15,6 @@
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
"isTestMode": true
}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-1.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-1.txt
index 74df747..b19297a 100644
--- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-1.txt
+++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-1.txt
@@ -12,5 +12,6 @@
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
"isTestMode": true
}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-2.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-2.txt
index 971922d..840f152 100644
--- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-2.txt
+++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---accept-2.txt
@@ -13,5 +13,6 @@
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
"isTestMode": true
}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-1.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-1.txt
index 74df747..b19297a 100644
--- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-1.txt
+++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-1.txt
@@ -12,5 +12,6 @@
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
"isTestMode": true
}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-2.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-2.txt
index 435a6b2..2e1bf3b 100644
--- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-2.txt
+++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---later-2.txt
@@ -13,5 +13,6 @@
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
"isTestMode": true
}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-1.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-1.txt
index 74df747..b19297a 100644
--- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-1.txt
+++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-1.txt
@@ -12,5 +12,6 @@
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
"isTestMode": true
}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-2.txt b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-2.txt
index d21a48f..74242ef 100644
--- a/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-2.txt
+++ b/e2e-tests/snapshots/telemetry.spec.ts_telemetry---reject-2.txt
@@ -13,5 +13,6 @@
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
"isTestMode": true
}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-1.txt b/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-1.txt
index bf5a86c..679208c 100644
--- a/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-1.txt
+++ b/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-1.txt
@@ -22,5 +22,6 @@
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
"isTestMode": true
}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-3.txt b/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-3.txt
index 0564e79..639ac63 100644
--- a/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-3.txt
+++ b/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-3.txt
@@ -22,5 +22,6 @@
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
"isTestMode": true
}
\ No newline at end of file
diff --git a/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-5.txt b/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-5.txt
index b215448..d245e7a 100644
--- a/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-5.txt
+++ b/e2e-tests/snapshots/thinking_budget.spec.ts_thinking-budget-5.txt
@@ -22,5 +22,6 @@
"enableProSmartFilesContextMode": true,
"selectedChatMode": "build",
"enableAutoFixProblems": false,
+ "enableAutoUpdate": true,
"isTestMode": true
}
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
index a1e46a7..168c2e9 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "dyad",
- "version": "0.8.0",
+ "version": "0.11.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "dyad",
- "version": "0.8.0",
+ "version": "0.11.1",
"license": "MIT",
"dependencies": {
"@ai-sdk/anthropic": "^1.2.8",
@@ -24,6 +24,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.7",
"@radix-ui/react-label": "^2.1.4",
"@radix-ui/react-popover": "^1.1.7",
+ "@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.2",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.2.2",
@@ -4680,6 +4681,78 @@
}
}
},
+ "node_modules/@radix-ui/react-scroll-area": {
+ "version": "1.2.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-scroll-area/-/react-scroll-area-1.2.9.tgz",
+ "integrity": "sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.2",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-presence": "1.1.4",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-scroll-area/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/@radix-ui/react-select": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.2.tgz",
diff --git a/package.json b/package.json
index 7fa6660..faac659 100644
--- a/package.json
+++ b/package.json
@@ -97,6 +97,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.7",
"@radix-ui/react-label": "^2.1.4",
"@radix-ui/react-popover": "^1.1.7",
+ "@radix-ui/react-scroll-area": "^1.2.9",
"@radix-ui/react-select": "^2.2.2",
"@radix-ui/react-separator": "^1.1.2",
"@radix-ui/react-slot": "^1.2.2",
diff --git a/src/components/AutoUpdateSwitch.tsx b/src/components/AutoUpdateSwitch.tsx
new file mode 100644
index 0000000..1e60206
--- /dev/null
+++ b/src/components/AutoUpdateSwitch.tsx
@@ -0,0 +1,36 @@
+import { useSettings } from "@/hooks/useSettings";
+import { Label } from "@/components/ui/label";
+import { Switch } from "@/components/ui/switch";
+import { toast } from "sonner";
+import { IpcClient } from "@/ipc/ipc_client";
+
+export function AutoUpdateSwitch() {
+ const { settings, updateSettings } = useSettings();
+
+ if (!settings) {
+ return null;
+ }
+
+ return (
+
+ {
+ updateSettings({ enableAutoUpdate: checked });
+ toast("Auto-update settings changed", {
+ description:
+ "You will need to restart Dyad for your settings to take effect.",
+ action: {
+ label: "Restart Dyad",
+ onClick: () => {
+ IpcClient.getInstance().restartDyad();
+ },
+ },
+ });
+ }}
+ />
+ Auto-update
+
+ );
+}
diff --git a/src/components/ProviderSettings.tsx b/src/components/ProviderSettings.tsx
index 5fe55dd..0292ca3 100644
--- a/src/components/ProviderSettings.tsx
+++ b/src/components/ProviderSettings.tsx
@@ -68,7 +68,7 @@ export function ProviderSettingsGrid() {
if (isLoading) {
return (
-
AI Providers
+
AI Providers
{[1, 2, 3, 4, 5].map((i) => (
@@ -86,7 +86,7 @@ export function ProviderSettingsGrid() {
if (error) {
return (
-
AI Providers
+
AI Providers
Error
@@ -100,7 +100,7 @@ export function ProviderSettingsGrid() {
return (
-
AI Providers
+
AI Providers
{providers
?.filter((p) => p.type !== "local")
@@ -116,7 +116,7 @@ export function ProviderSettingsGrid() {
className="p-4 cursor-pointer"
onClick={() => handleProviderClick(provider.id)}
>
-
+
{provider.name}
{isProviderSetup(provider.id) ? (
@@ -178,8 +178,8 @@ export function ProviderSettingsGrid() {
onClick={() => setIsDialogOpen(true)}
>
-
-
+
+
Add custom provider
diff --git a/src/components/SettingsList.tsx b/src/components/SettingsList.tsx
new file mode 100644
index 0000000..95610f1
--- /dev/null
+++ b/src/components/SettingsList.tsx
@@ -0,0 +1,88 @@
+import { ScrollArea } from "@/components/ui/scroll-area";
+import { cn } from "@/lib/utils";
+import { useNavigate } from "@tanstack/react-router";
+import { useEffect, useState } from "react";
+
+const SETTINGS_SECTIONS = [
+ { id: "general-settings", label: "General" },
+ { id: "workflow-settings", label: "Workflow" },
+ { id: "ai-settings", label: "AI" },
+ { id: "provider-settings", label: "Model Providers" },
+ { id: "telemetry", label: "Telemetry" },
+ { id: "integrations", label: "Integrations" },
+ { id: "experiments", label: "Experiments" },
+ { id: "danger-zone", label: "Danger Zone" },
+];
+
+export function SettingsList({ show }: { show: boolean }) {
+ const navigate = useNavigate();
+ const [activeSection, setActiveSection] = useState(
+ "general-settings",
+ );
+
+ useEffect(() => {
+ const observer = new IntersectionObserver(
+ (entries) => {
+ for (const entry of entries) {
+ if (entry.isIntersecting) {
+ setActiveSection(entry.target.id);
+ return;
+ }
+ }
+ },
+ { rootMargin: "-20% 0px -80% 0px", threshold: 0 },
+ );
+
+ for (const section of SETTINGS_SECTIONS) {
+ const el = document.getElementById(section.id);
+ if (el) {
+ observer.observe(el);
+ }
+ }
+
+ return () => {
+ observer.disconnect();
+ };
+ }, []);
+
+ if (!show) {
+ return null;
+ }
+
+ const handleScrollAndNavigateTo = async (id: string) => {
+ await navigate({
+ to: "/settings",
+ });
+ const element = document.getElementById(id);
+ if (element) {
+ element.scrollIntoView({ behavior: "smooth", block: "start" });
+ setActiveSection(id);
+ }
+ };
+
+ return (
+
+
+
Settings
+
+
+
+ {SETTINGS_SECTIONS.map((section) => (
+ handleScrollAndNavigateTo(section.id)}
+ className={cn(
+ "w-full text-left px-3 py-2 rounded-md text-sm transition-colors",
+ activeSection === section.id
+ ? "bg-sidebar-accent text-sidebar-accent-foreground font-semibold"
+ : "hover:bg-sidebar-accent",
+ )}
+ >
+ {section.label}
+
+ ))}
+
+
+
+ );
+}
diff --git a/src/components/app-sidebar.tsx b/src/components/app-sidebar.tsx
index 21eb4c1..41df181 100644
--- a/src/components/app-sidebar.tsx
+++ b/src/components/app-sidebar.tsx
@@ -20,6 +20,7 @@ import {
import { ChatList } from "./ChatList";
import { AppList } from "./AppList";
import { HelpDialog } from "./HelpDialog"; // Import the new dialog
+import { SettingsList } from "./SettingsList";
// Menu items.
const items = [
@@ -49,6 +50,7 @@ const items = [
type HoverState =
| "start-hover:app"
| "start-hover:chat"
+ | "start-hover:settings"
| "clear-hover"
| "no-hover";
@@ -60,10 +62,7 @@ export function AppSidebar() {
const [isDropdownOpen] = useAtom(dropdownOpenAtom);
useEffect(() => {
- if (
- (hoverState === "start-hover:app" || hoverState === "start-hover:chat") &&
- state === "collapsed"
- ) {
+ if (hoverState.startsWith("start-hover") && state === "collapsed") {
expandedByHover.current = true;
toggleSidebar();
}
@@ -84,17 +83,22 @@ export function AppSidebar() {
routerState.location.pathname === "/" ||
routerState.location.pathname.startsWith("/app-details");
const isChatRoute = routerState.location.pathname === "/chat";
+ const isSettingsRoute = routerState.location.pathname.startsWith("/settings");
let selectedItem: string | null = null;
if (hoverState === "start-hover:app") {
selectedItem = "Apps";
} else if (hoverState === "start-hover:chat") {
selectedItem = "Chat";
+ } else if (hoverState === "start-hover:settings") {
+ selectedItem = "Settings";
} else if (state === "expanded") {
if (isAppRoute) {
selectedItem = "Apps";
} else if (isChatRoute) {
selectedItem = "Chat";
+ } else if (isSettingsRoute) {
+ selectedItem = "Settings";
}
}
@@ -122,6 +126,7 @@ export function AppSidebar() {
@@ -188,6 +193,8 @@ function AppIcons({
onHoverChange("start-hover:app");
} else if (item.title === "Chat") {
onHoverChange("start-hover:chat");
+ } else if (item.title === "Settings") {
+ onHoverChange("start-hover:settings");
}
}}
>
diff --git a/src/components/ui/scroll-area.tsx b/src/components/ui/scroll-area.tsx
new file mode 100644
index 0000000..80b954a
--- /dev/null
+++ b/src/components/ui/scroll-area.tsx
@@ -0,0 +1,46 @@
+import * as React from "react";
+import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
+
+import { cn } from "@/lib/utils";
+
+const ScrollArea = React.forwardRef<
+ React.ElementRef
,
+ React.ComponentPropsWithoutRef
+>(({ className, children, ...props }, ref) => (
+
+
+ {children}
+
+
+
+
+));
+ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
+
+const ScrollBar = React.forwardRef<
+ React.ElementRef,
+ React.ComponentPropsWithoutRef
+>(({ className, orientation = "vertical", ...props }, ref) => (
+
+
+
+));
+ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName;
+
+export { ScrollArea, ScrollBar };
diff --git a/src/ipc/handlers/app_handlers.ts b/src/ipc/handlers/app_handlers.ts
index ad22f04..1545df6 100644
--- a/src/ipc/handlers/app_handlers.ts
+++ b/src/ipc/handlers/app_handlers.ts
@@ -1,4 +1,4 @@
-import { ipcMain } from "electron";
+import { ipcMain, app } from "electron";
import { db, getDatabasePath } from "../../db";
import { apps, chats } from "../../db/schema";
import { desc, eq } from "drizzle-orm";
@@ -186,6 +186,11 @@ async function killProcessOnPort(port: number): Promise {
}
export function registerAppHandlers() {
+ handle("restart-dyad", async () => {
+ app.relaunch();
+ app.quit();
+ });
+
handle(
"create-app",
async (
diff --git a/src/ipc/ipc_client.ts b/src/ipc/ipc_client.ts
index aa43a99..0fb4fb0 100644
--- a/src/ipc/ipc_client.ts
+++ b/src/ipc/ipc_client.ts
@@ -165,6 +165,10 @@ export class IpcClient {
return IpcClient.instance;
}
+ public async restartDyad(): Promise {
+ await this.ipcRenderer.invoke("restart-dyad");
+ }
+
public async reloadEnvPath(): Promise {
await this.ipcRenderer.invoke("reload-env-path");
}
diff --git a/src/lib/schemas.ts b/src/lib/schemas.ts
index 82c5a7a..4965ed7 100644
--- a/src/lib/schemas.ts
+++ b/src/lib/schemas.ts
@@ -151,6 +151,7 @@ export const UserSettingsSchema = z.object({
enableAutoFixProblems: z.boolean().optional(),
enableNativeGit: z.boolean().optional(),
+ enableAutoUpdate: z.boolean(),
////////////////////////////////
// E2E TESTING ONLY.
diff --git a/src/main.ts b/src/main.ts
index bc90ad0..af3e4f0 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -17,7 +17,11 @@ log.scope.labelPadding = false;
const logger = log.scope("main");
-updateElectronApp(); // additional configuration options available
+// Check settings before enabling auto-update
+const settings = readSettings();
+if (settings.enableAutoUpdate) {
+ updateElectronApp({ logger }); // additional configuration options available
+}
// Load environment variables from .env file
dotenv.config();
diff --git a/src/main/settings.ts b/src/main/settings.ts
index 2b79ddf..fc7f376 100644
--- a/src/main/settings.ts
+++ b/src/main/settings.ts
@@ -21,6 +21,7 @@ const DEFAULT_SETTINGS: UserSettings = {
enableProSmartFilesContextMode: true,
selectedChatMode: "build",
enableAutoFixProblems: false,
+ enableAutoUpdate: true,
};
const SETTINGS_FILE = "user-settings.json";
diff --git a/src/pages/settings.tsx b/src/pages/settings.tsx
index 3f285c4..6c279e3 100644
--- a/src/pages/settings.tsx
+++ b/src/pages/settings.tsx
@@ -18,9 +18,9 @@ import { SupabaseIntegration } from "@/components/SupabaseIntegration";
import { Switch } from "@/components/ui/switch";
import { Label } from "@/components/ui/label";
import { AutoFixProblemsSwitch } from "@/components/AutoFixProblemsSwitch";
+import { AutoUpdateSwitch } from "@/components/AutoUpdateSwitch";
export default function SettingsPage() {
- const { theme, setTheme } = useTheme();
const [isResetDialogOpen, setIsResetDialogOpen] = useState(false);
const [isResetting, setIsResetting] = useState(false);
const appVersion = useAppVersion();
@@ -60,108 +60,25 @@ export default function SettingsPage() {
Settings
-
- {/* App Version Section */}
-
- App Version:
-
- {appVersion ? appVersion : "-"}
-
-
-
-
- General Settings
-
+
+
+
-
-
-
- Theme
-
-
-
- {(["system", "light", "dark"] as const).map((option) => (
- setTheme(option)}
- className={`
- px-4 py-1.5 text-sm font-medium rounded-md
- transition-all duration-200
- ${
- theme === option
- ? "bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm"
- : "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
- }
- `}
- >
- {option.charAt(0).toUpperCase() + option.slice(1)}
-
- ))}
-
-
-
-
-
-
-
- This will automatically approve code changes and run them.
-
-
-
-
-
-
- This will automatically fix TypeScript errors.
-
-
-
-
-
- {
- updateSettings({
- enableNativeGit: checked,
- });
- }}
- />
- Enable Native Git
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
-
+
Telemetry
@@ -182,7 +99,10 @@ export default function SettingsPage() {
{/* Integrations Section */}
-
+
Integrations
@@ -193,41 +113,74 @@ export default function SettingsPage() {
{/* Experiments Section */}
-
+
Experiments
- {/* Enable File Editing Experiment */}
-
-
- Enable File Editing
-
-
{
- updateSettings({
- experiments: {
- ...settings?.experiments,
- enableFileEditing: checked,
- },
- });
- }}
- />
+
+
+ {
+ updateSettings({
+ enableNativeGit: checked,
+ });
+ }}
+ />
+ Enable Native Git
+
+
+
+ {/* Enable File Editing Experiment */}
+
+
+
+ updateSettings({
+ experiments: {
+ ...settings?.experiments,
+ enableFileEditing: checked,
+ },
+ })
+ }
+ />
+
+ Enable File Editing
+
+
+
+ File editing is not reliable and requires you to manually
+ commit changes and update Supabase edge functions.
+
-
- File editing is not reliable and requires you to manually commit
- changes and update Supabase edge functions.
-
{/* Danger Zone */}
-
+
Danger Zone
@@ -268,3 +221,108 @@ export default function SettingsPage() {
);
}
+
+export function GeneralSettings({ appVersion }: { appVersion: string | null }) {
+ const { theme, setTheme } = useTheme();
+
+ return (
+
+
+ General Settings
+
+
+
+
+
+ Theme
+
+
+
+ {(["system", "light", "dark"] as const).map((option) => (
+ setTheme(option)}
+ className={`
+ px-4 py-1.5 text-sm font-medium rounded-md
+ transition-all duration-200
+ ${
+ theme === option
+ ? "bg-white dark:bg-gray-600 text-gray-900 dark:text-white shadow-sm"
+ : "text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white"
+ }
+ `}
+ >
+ {option.charAt(0).toUpperCase() + option.slice(1)}
+
+ ))}
+
+
+
+
+
+
+
+ This will automatically update the app when new versions are
+ available.
+
+
+
+
+ App Version:
+
+ {appVersion ? appVersion : "-"}
+
+
+
+ );
+}
+
+export function WorkflowSettings() {
+ return (
+
+
+ Workflow Settings
+
+
+
+
+
+ This will automatically approve code changes and run them.
+
+
+
+
+
+
+ This will automatically fix TypeScript errors.
+
+
+
+ );
+}
+export function AISettings() {
+ return (
+
+
+ AI Settings
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/src/preload.ts b/src/preload.ts
index 51456a8..4394445 100644
--- a/src/preload.ts
+++ b/src/preload.ts
@@ -89,6 +89,7 @@ const validInvokeChannels = [
"open-ios",
"open-android",
"check-problems",
+ "restart-dyad",
// Test-only channels
// These should ALWAYS be guarded with IS_TEST_BUILD in the main process.
// We can't detect with IS_TEST_BUILD in the preload script because