From 7a0dad03f88ed14e70ee1b59ffaa25d265b86b80 Mon Sep 17 00:00:00 2001 From: Reekin Date: Sun, 15 Mar 2026 19:11:44 +0800 Subject: [PATCH 1/2] Add automatic app update check setting --- src-tauri/src/types.rs | 11 +++ src/features/app/components/MainApp.tsx | 1 + .../app/hooks/useUpdaterController.ts | 3 + .../settings/components/SettingsView.test.tsx | 77 +++++++++++++++++++ .../sections/SettingsAboutSection.tsx | 33 +++++++- .../sections/SettingsSectionContainers.tsx | 2 +- src/features/settings/hooks/useAppSettings.ts | 1 + .../hooks/useSettingsViewOrchestration.ts | 4 + src/features/update/hooks/useUpdater.test.ts | 17 ++++ src/features/update/hooks/useUpdater.ts | 11 ++- src/types.ts | 1 + 11 files changed, 155 insertions(+), 6 deletions(-) diff --git a/src-tauri/src/types.rs b/src-tauri/src/types.rs index cbb8afbaa..9ab969efa 100644 --- a/src-tauri/src/types.rs +++ b/src-tauri/src/types.rs @@ -504,6 +504,11 @@ pub(crate) struct AppSettings { pub(crate) chat_history_scrollback_items: Option, #[serde(default, rename = "threadTitleAutogenerationEnabled")] pub(crate) thread_title_autogeneration_enabled: bool, + #[serde( + default = "default_automatic_app_update_checks_enabled", + rename = "automaticAppUpdateChecksEnabled" + )] + pub(crate) automatic_app_update_checks_enabled: bool, #[serde(default = "default_ui_font_family", rename = "uiFontFamily")] pub(crate) ui_font_family: String, #[serde(default = "default_code_font_family", rename = "codeFontFamily")] @@ -710,6 +715,10 @@ fn default_chat_history_scrollback_items() -> Option { Some(200) } +fn default_automatic_app_update_checks_enabled() -> bool { + true +} + fn default_ui_font_family() -> String { "system-ui, -apple-system, BlinkMacSystemFont, \"Segoe UI\", Roboto, \"Helvetica Neue\", Arial, sans-serif".to_string() } @@ -1146,6 +1155,7 @@ impl Default for AppSettings { show_message_file_path: default_show_message_file_path(), chat_history_scrollback_items: default_chat_history_scrollback_items(), thread_title_autogeneration_enabled: false, + automatic_app_update_checks_enabled: true, ui_font_family: default_ui_font_family(), code_font_family: default_code_font_family(), code_font_size: default_code_font_size(), @@ -1310,6 +1320,7 @@ mod tests { assert!(settings.show_message_file_path); assert_eq!(settings.chat_history_scrollback_items, Some(200)); assert!(!settings.thread_title_autogeneration_enabled); + assert!(settings.automatic_app_update_checks_enabled); assert!(settings.ui_font_family.contains("system-ui")); assert!(settings.code_font_family.contains("ui-monospace")); assert_eq!(settings.code_font_size, 11); diff --git a/src/features/app/components/MainApp.tsx b/src/features/app/components/MainApp.tsx index 8ea9d9036..cb4393f81 100644 --- a/src/features/app/components/MainApp.tsx +++ b/src/features/app/components/MainApp.tsx @@ -631,6 +631,7 @@ export default function MainApp() { handleTestSystemNotification, } = useUpdaterController({ enabled: updaterEnabled, + autoCheckOnMount: appSettings.automaticAppUpdateChecksEnabled, notificationSoundsEnabled: appSettings.notificationSoundsEnabled, systemNotificationsEnabled: appSettings.systemNotificationsEnabled, subagentSystemNotificationsEnabled: diff --git a/src/features/app/hooks/useUpdaterController.ts b/src/features/app/hooks/useUpdaterController.ts index b3ca64ed5..ed640e076 100644 --- a/src/features/app/hooks/useUpdaterController.ts +++ b/src/features/app/hooks/useUpdaterController.ts @@ -11,6 +11,7 @@ import type { DebugEntry } from "../../../types"; type Params = { enabled?: boolean; + autoCheckOnMount?: boolean; notificationSoundsEnabled: boolean; systemNotificationsEnabled: boolean; subagentSystemNotificationsEnabled: boolean; @@ -24,6 +25,7 @@ type Params = { export function useUpdaterController({ enabled = true, + autoCheckOnMount = true, notificationSoundsEnabled, systemNotificationsEnabled, subagentSystemNotificationsEnabled, @@ -43,6 +45,7 @@ export function useUpdaterController({ dismissPostUpdateNotice, } = useUpdater({ enabled, + autoCheckOnMount, onDebug, }); const isWindowFocused = useWindowFocusState(); diff --git a/src/features/settings/components/SettingsView.test.tsx b/src/features/settings/components/SettingsView.test.tsx index e8d5a7e03..80d3ff510 100644 --- a/src/features/settings/components/SettingsView.test.tsx +++ b/src/features/settings/components/SettingsView.test.tsx @@ -13,9 +13,11 @@ import { describe, expect, it, vi } from "vitest"; import type { AppSettings, WorkspaceInfo } from "@/types"; import { connectWorkspace, + getAppBuildType, getAgentsSettings, getConfigModel, getExperimentalFeatureList, + isMobileRuntime, getModelList, listWorkspaces, } from "@services/tauri"; @@ -34,22 +36,28 @@ vi.mock("@services/tauri", async () => { return { ...actual, connectWorkspace: vi.fn(), + getAppBuildType: vi.fn(), getModelList: vi.fn(), getConfigModel: vi.fn(), getExperimentalFeatureList: vi.fn(), getAgentsSettings: vi.fn(), + isMobileRuntime: vi.fn(), listWorkspaces: vi.fn(), }; }); const connectWorkspaceMock = vi.mocked(connectWorkspace); +const getAppBuildTypeMock = vi.mocked(getAppBuildType); const getConfigModelMock = vi.mocked(getConfigModel); const getModelListMock = vi.mocked(getModelList); const getExperimentalFeatureListMock = vi.mocked(getExperimentalFeatureList); const getAgentsSettingsMock = vi.mocked(getAgentsSettings); +const isMobileRuntimeMock = vi.mocked(isMobileRuntime); const listWorkspacesMock = vi.mocked(listWorkspaces); connectWorkspaceMock.mockResolvedValue(undefined); +getAppBuildTypeMock.mockResolvedValue("release"); getConfigModelMock.mockResolvedValue(null); +isMobileRuntimeMock.mockResolvedValue(false); listWorkspacesMock.mockResolvedValue([]); getAgentsSettingsMock.mockResolvedValue({ configPath: "/Users/me/.codex/config.toml", @@ -105,6 +113,7 @@ const baseSettings: AppSettings = { showMessageFilePath: true, chatHistoryScrollbackItems: 200, threadTitleAutogenerationEnabled: false, + automaticAppUpdateChecksEnabled: true, uiFontFamily: 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif', codeFontFamily: @@ -266,6 +275,50 @@ const renderComposerSection = ( return { onUpdateAppSettings }; }; +const renderAboutSection = ( + options: { + appSettings?: Partial; + onUpdateAppSettings?: ComponentProps["onUpdateAppSettings"]; + } = {}, +) => { + cleanup(); + const onUpdateAppSettings = + options.onUpdateAppSettings ?? vi.fn().mockResolvedValue(undefined); + const props: ComponentProps = { + reduceTransparency: false, + onToggleTransparency: vi.fn(), + appSettings: { ...baseSettings, ...options.appSettings }, + openAppIconById: {}, + onUpdateAppSettings, + workspaceGroups: [], + groupedWorkspaces: [], + ungroupedLabel: "Ungrouped", + onClose: vi.fn(), + onMoveWorkspace: vi.fn(), + onDeleteWorkspace: vi.fn(), + onCreateWorkspaceGroup: vi.fn().mockResolvedValue(null), + onRenameWorkspaceGroup: vi.fn().mockResolvedValue(null), + onMoveWorkspaceGroup: vi.fn().mockResolvedValue(null), + onDeleteWorkspaceGroup: vi.fn().mockResolvedValue(null), + onAssignWorkspaceGroup: vi.fn().mockResolvedValue(null), + onRunDoctor: vi.fn().mockResolvedValue(createDoctorResult()), + onUpdateWorkspaceSettings: vi.fn().mockResolvedValue(undefined), + scaleShortcutTitle: "Scale shortcut", + scaleShortcutText: "Use Command +/-", + onTestNotificationSound: vi.fn(), + onTestSystemNotification: vi.fn(), + dictationModelStatus: null, + onDownloadDictationModel: vi.fn(), + onCancelDictationDownload: vi.fn(), + onRemoveDictationModel: vi.fn(), + }; + + render(); + fireEvent.click(screen.getByRole("button", { name: "About" })); + + return { onUpdateAppSettings }; +}; + const renderFeaturesSection = ( options: { appSettings?: Partial; @@ -672,6 +725,30 @@ describe("SettingsView Display", () => { }); }); +describe("SettingsView About", () => { + it("toggles automatic app update checks", async () => { + const onUpdateAppSettings = vi.fn().mockResolvedValue(undefined); + renderAboutSection({ + onUpdateAppSettings, + appSettings: { automaticAppUpdateChecksEnabled: false }, + }); + + const row = screen + .getByText("Automatically check for app updates") + .closest(".settings-toggle-row") as HTMLElement | null; + if (!row) { + throw new Error("Expected automatic app update checks row"); + } + fireEvent.click(within(row).getByRole("button")); + + await waitFor(() => { + expect(onUpdateAppSettings).toHaveBeenCalledWith( + expect.objectContaining({ automaticAppUpdateChecksEnabled: true }), + ); + }); + }); +}); + describe("SettingsView Environments", () => { it("saves the setup script for the selected project", async () => { const onUpdateWorkspaceSettings = vi.fn().mockResolvedValue(undefined); diff --git a/src/features/settings/components/sections/SettingsAboutSection.tsx b/src/features/settings/components/sections/SettingsAboutSection.tsx index f78e35d35..d8ddcb314 100644 --- a/src/features/settings/components/sections/SettingsAboutSection.tsx +++ b/src/features/settings/components/sections/SettingsAboutSection.tsx @@ -1,11 +1,21 @@ import { useEffect, useState } from "react"; +import type { AppSettings } from "@/types"; import { getAppBuildType, isMobileRuntime, type AppBuildType, } from "@services/tauri"; import { useUpdater } from "@/features/update/hooks/useUpdater"; -import { SettingsSection } from "@/features/design-system/components/settings/SettingsPrimitives"; +import { + SettingsSection, + SettingsToggleRow, + SettingsToggleSwitch, +} from "@/features/design-system/components/settings/SettingsPrimitives"; + +type SettingsAboutSectionProps = { + appSettings: AppSettings; + onUpdateAppSettings: (next: AppSettings) => Promise; +}; function formatBytes(value: number) { if (!Number.isFinite(value) || value <= 0) { @@ -21,11 +31,15 @@ function formatBytes(value: number) { return `${size.toFixed(size >= 10 ? 0 : 1)} ${units[unitIndex]}`; } -export function SettingsAboutSection() { +export function SettingsAboutSection({ + appSettings, + onUpdateAppSettings, +}: SettingsAboutSectionProps) { const [appBuildType, setAppBuildType] = useState("unknown"); const [updaterEnabled, setUpdaterEnabled] = useState(false); const { state: updaterState, checkForUpdates, startUpdate } = useUpdater({ enabled: updaterEnabled, + autoCheckOnMount: false, }); useEffect(() => { @@ -96,6 +110,21 @@ export function SettingsAboutSection() {
App Updates
+ + + void onUpdateAppSettings({ + ...appSettings, + automaticAppUpdateChecksEnabled: + !appSettings.automaticAppUpdateChecksEnabled, + }) + } + /> +
Currently running version {__APP_VERSION__}
diff --git a/src/features/settings/components/sections/SettingsSectionContainers.tsx b/src/features/settings/components/sections/SettingsSectionContainers.tsx index 2606a15ec..d01bb3420 100644 --- a/src/features/settings/components/sections/SettingsSectionContainers.tsx +++ b/src/features/settings/components/sections/SettingsSectionContainers.tsx @@ -33,7 +33,7 @@ export function SettingsSectionContainers({ return ; } if (activeSection === "about") { - return ; + return ; } if (activeSection === "composer") { return ; diff --git a/src/features/settings/hooks/useAppSettings.ts b/src/features/settings/hooks/useAppSettings.ts index e4dafaccc..20bc5901f 100644 --- a/src/features/settings/hooks/useAppSettings.ts +++ b/src/features/settings/hooks/useAppSettings.ts @@ -171,6 +171,7 @@ function buildDefaultSettings(): AppSettings { showMessageFilePath: true, chatHistoryScrollbackItems: CHAT_SCROLLBACK_DEFAULT, threadTitleAutogenerationEnabled: false, + automaticAppUpdateChecksEnabled: true, uiFontFamily: DEFAULT_UI_FONT_FAMILY, codeFontFamily: DEFAULT_CODE_FONT_FAMILY, codeFontSize: CODE_FONT_SIZE_DEFAULT, diff --git a/src/features/settings/hooks/useSettingsViewOrchestration.ts b/src/features/settings/hooks/useSettingsViewOrchestration.ts index c8e0bd147..5c8694cae 100644 --- a/src/features/settings/hooks/useSettingsViewOrchestration.ts +++ b/src/features/settings/hooks/useSettingsViewOrchestration.ts @@ -211,6 +211,10 @@ export function useSettingsViewOrchestration({ const agentsSectionProps = useSettingsAgentsSection({ projects }); return { + aboutSectionProps: { + appSettings, + onUpdateAppSettings, + }, projectsSectionProps, environmentsSectionProps, displaySectionProps, diff --git a/src/features/update/hooks/useUpdater.test.ts b/src/features/update/hooks/useUpdater.test.ts index da8757ed9..af78c062f 100644 --- a/src/features/update/hooks/useUpdater.test.ts +++ b/src/features/update/hooks/useUpdater.test.ts @@ -199,6 +199,23 @@ describe("useUpdater", () => { expect(result.current.state.stage).toBe("idle"); }); + it("skips automatic startup checks when auto-check is disabled but still allows manual checks", async () => { + checkMock.mockResolvedValue(null); + + const { result } = renderHook(() => + useUpdater({ autoCheckOnMount: false }), + ); + + expect(checkMock).not.toHaveBeenCalled(); + + await act(async () => { + await result.current.checkForUpdates({ announceNoUpdate: true }); + }); + + expect(checkMock).toHaveBeenCalledTimes(1); + expect(result.current.state.stage).toBe("latest"); + }); + it("loads post-update release notes after restart when marker matches current version", async () => { window.localStorage.setItem( STORAGE_KEY_PENDING_POST_UPDATE_VERSION, diff --git a/src/features/update/hooks/useUpdater.ts b/src/features/update/hooks/useUpdater.ts index fc607fb63..0e382b26e 100644 --- a/src/features/update/hooks/useUpdater.ts +++ b/src/features/update/hooks/useUpdater.ts @@ -57,10 +57,15 @@ export type PostUpdateNoticeState = PostUpdateNotice | null; type UseUpdaterOptions = { enabled?: boolean; + autoCheckOnMount?: boolean; onDebug?: (entry: DebugEntry) => void; }; -export function useUpdater({ enabled = true, onDebug }: UseUpdaterOptions) { +export function useUpdater({ + enabled = true, + autoCheckOnMount = true, + onDebug, +}: UseUpdaterOptions) { const [state, setState] = useState({ stage: "idle" }); const [postUpdateNotice, setPostUpdateNotice] = useState( null, @@ -205,11 +210,11 @@ export function useUpdater({ enabled = true, onDebug }: UseUpdaterOptions) { }, [checkForUpdates, enabled, onDebug]); useEffect(() => { - if (!enabled || import.meta.env.DEV || !isTauri()) { + if (!enabled || !autoCheckOnMount || import.meta.env.DEV || !isTauri()) { return; } void checkForUpdates(); - }, [checkForUpdates, enabled]); + }, [autoCheckOnMount, checkForUpdates, enabled]); useEffect(() => { if (!enabled || !isTauri()) { diff --git a/src/types.ts b/src/types.ts index aa68a1eb0..e14b95e35 100644 --- a/src/types.ts +++ b/src/types.ts @@ -270,6 +270,7 @@ export type AppSettings = { showMessageFilePath: boolean; chatHistoryScrollbackItems: number | null; threadTitleAutogenerationEnabled: boolean; + automaticAppUpdateChecksEnabled: boolean; uiFontFamily: string; codeFontFamily: string; codeFontSize: number; From b0212af0850589490a14dfe099fb71172c087024 Mon Sep 17 00:00:00 2001 From: Reekin Date: Mon, 16 Mar 2026 00:39:53 +0800 Subject: [PATCH 2/2] fix: respect auto update setting after app settings load --- src/features/app/components/MainApp.tsx | 16 +++++++++++++++- src/features/app/hooks/useMainAppModals.ts | 3 +++ .../settings/components/SettingsView.test.tsx | 16 ++++++++++------ .../settings/components/SettingsView.tsx | 3 +++ .../components/sections/SettingsAboutSection.tsx | 14 +++++--------- .../hooks/useSettingsViewOrchestration.ts | 4 +++- src/features/update/hooks/useUpdater.ts | 5 +++++ 7 files changed, 44 insertions(+), 17 deletions(-) diff --git a/src/features/app/components/MainApp.tsx b/src/features/app/components/MainApp.tsx index 144ba0056..029a1f78d 100644 --- a/src/features/app/components/MainApp.tsx +++ b/src/features/app/components/MainApp.tsx @@ -632,7 +632,8 @@ export default function MainApp() { handleTestSystemNotification, } = useUpdaterController({ enabled: updaterEnabled, - autoCheckOnMount: appSettings.automaticAppUpdateChecksEnabled, + autoCheckOnMount: + !appSettingsLoading && appSettings.automaticAppUpdateChecksEnabled, notificationSoundsEnabled: appSettings.notificationSoundsEnabled, systemNotificationsEnabled: appSettings.systemNotificationsEnabled, subagentSystemNotificationsEnabled: @@ -1014,6 +1015,18 @@ export default function MainApp() { [queueSaveSettings, setAppSettings], ); + const handleToggleAutomaticAppUpdateChecks = useCallback(() => { + setAppSettings((current) => { + const nextSettings = { + ...current, + automaticAppUpdateChecksEnabled: + !current.automaticAppUpdateChecksEnabled, + }; + void queueSaveSettings(nextSettings); + return nextSettings; + }); + }, [queueSaveSettings, setAppSettings]); + const openAppIconById = useOpenAppIcons(appSettings.openAppTargets); const persistProjectCopiesFolder = useCallback( @@ -1119,6 +1132,7 @@ export default function MainApp() { appSettings, openAppIconById, queueSaveSettings, + handleToggleAutomaticAppUpdateChecks, doctor, codexUpdate, updateWorkspaceSettings, diff --git a/src/features/app/hooks/useMainAppModals.ts b/src/features/app/hooks/useMainAppModals.ts index bc5145d63..02847b018 100644 --- a/src/features/app/hooks/useMainAppModals.ts +++ b/src/features/app/hooks/useMainAppModals.ts @@ -107,6 +107,7 @@ type UseMainAppModalsArgs = { appSettings: AppSettings; openAppIconById: Record; queueSaveSettings: (next: AppSettings) => Promise; + handleToggleAutomaticAppUpdateChecks: () => void; doctor: ( codexBin: string | null, codexArgs: string | null, @@ -268,6 +269,8 @@ export function useMainAppModals({ onUpdateAppSettings: async (next) => { await Promise.resolve(settings.queueSaveSettings(next)); }, + onToggleAutomaticAppUpdateChecks: + settings.handleToggleAutomaticAppUpdateChecks, onRunDoctor: settings.doctor, onRunCodexUpdate: settings.codexUpdate, onUpdateWorkspaceSettings: async (id, nextSettings) => { diff --git a/src/features/settings/components/SettingsView.test.tsx b/src/features/settings/components/SettingsView.test.tsx index 80d3ff510..ddd0fbada 100644 --- a/src/features/settings/components/SettingsView.test.tsx +++ b/src/features/settings/components/SettingsView.test.tsx @@ -279,17 +279,23 @@ const renderAboutSection = ( options: { appSettings?: Partial; onUpdateAppSettings?: ComponentProps["onUpdateAppSettings"]; + onToggleAutomaticAppUpdateChecks?: ComponentProps< + typeof SettingsView + >["onToggleAutomaticAppUpdateChecks"]; } = {}, ) => { cleanup(); const onUpdateAppSettings = options.onUpdateAppSettings ?? vi.fn().mockResolvedValue(undefined); + const onToggleAutomaticAppUpdateChecks = + options.onToggleAutomaticAppUpdateChecks ?? vi.fn(); const props: ComponentProps = { reduceTransparency: false, onToggleTransparency: vi.fn(), appSettings: { ...baseSettings, ...options.appSettings }, openAppIconById: {}, onUpdateAppSettings, + onToggleAutomaticAppUpdateChecks, workspaceGroups: [], groupedWorkspaces: [], ungroupedLabel: "Ungrouped", @@ -316,7 +322,7 @@ const renderAboutSection = ( render(); fireEvent.click(screen.getByRole("button", { name: "About" })); - return { onUpdateAppSettings }; + return { onUpdateAppSettings, onToggleAutomaticAppUpdateChecks }; }; const renderFeaturesSection = ( @@ -727,9 +733,9 @@ describe("SettingsView Display", () => { describe("SettingsView About", () => { it("toggles automatic app update checks", async () => { - const onUpdateAppSettings = vi.fn().mockResolvedValue(undefined); + const onToggleAutomaticAppUpdateChecks = vi.fn(); renderAboutSection({ - onUpdateAppSettings, + onToggleAutomaticAppUpdateChecks, appSettings: { automaticAppUpdateChecksEnabled: false }, }); @@ -742,9 +748,7 @@ describe("SettingsView About", () => { fireEvent.click(within(row).getByRole("button")); await waitFor(() => { - expect(onUpdateAppSettings).toHaveBeenCalledWith( - expect.objectContaining({ automaticAppUpdateChecksEnabled: true }), - ); + expect(onToggleAutomaticAppUpdateChecks).toHaveBeenCalledTimes(1); }); }); }); diff --git a/src/features/settings/components/SettingsView.tsx b/src/features/settings/components/SettingsView.tsx index 62327c2a8..cdd67326b 100644 --- a/src/features/settings/components/SettingsView.tsx +++ b/src/features/settings/components/SettingsView.tsx @@ -42,6 +42,7 @@ export type SettingsViewProps = { appSettings: AppSettings; openAppIconById: Record; onUpdateAppSettings: (next: AppSettings) => Promise; + onToggleAutomaticAppUpdateChecks?: () => void; onRunDoctor: ( codexBin: string | null, codexArgs: string | null, @@ -83,6 +84,7 @@ export function SettingsView({ appSettings, openAppIconById, onUpdateAppSettings, + onToggleAutomaticAppUpdateChecks, onRunDoctor, onRunCodexUpdate, onUpdateWorkspaceSettings, @@ -114,6 +116,7 @@ export function SettingsView({ appSettings, openAppIconById, onUpdateAppSettings, + onToggleAutomaticAppUpdateChecks, onRunDoctor, onRunCodexUpdate, onUpdateWorkspaceSettings, diff --git a/src/features/settings/components/sections/SettingsAboutSection.tsx b/src/features/settings/components/sections/SettingsAboutSection.tsx index d8ddcb314..d22af22e6 100644 --- a/src/features/settings/components/sections/SettingsAboutSection.tsx +++ b/src/features/settings/components/sections/SettingsAboutSection.tsx @@ -14,7 +14,7 @@ import { type SettingsAboutSectionProps = { appSettings: AppSettings; - onUpdateAppSettings: (next: AppSettings) => Promise; + onToggleAutomaticAppUpdateChecks?: () => void; }; function formatBytes(value: number) { @@ -33,7 +33,7 @@ function formatBytes(value: number) { export function SettingsAboutSection({ appSettings, - onUpdateAppSettings, + onToggleAutomaticAppUpdateChecks, }: SettingsAboutSectionProps) { const [appBuildType, setAppBuildType] = useState("unknown"); const [updaterEnabled, setUpdaterEnabled] = useState(false); @@ -116,13 +116,9 @@ export function SettingsAboutSection({ > - void onUpdateAppSettings({ - ...appSettings, - automaticAppUpdateChecksEnabled: - !appSettings.automaticAppUpdateChecksEnabled, - }) - } + onClick={() => { + onToggleAutomaticAppUpdateChecks?.(); + }} />
diff --git a/src/features/settings/hooks/useSettingsViewOrchestration.ts b/src/features/settings/hooks/useSettingsViewOrchestration.ts index 5c8694cae..8850e47ca 100644 --- a/src/features/settings/hooks/useSettingsViewOrchestration.ts +++ b/src/features/settings/hooks/useSettingsViewOrchestration.ts @@ -34,6 +34,7 @@ type UseSettingsViewOrchestrationArgs = { appSettings: AppSettings; openAppIconById: Record; onUpdateAppSettings: (next: AppSettings) => Promise; + onToggleAutomaticAppUpdateChecks?: () => void; onRunDoctor: ( codexBin: string | null, codexArgs: string | null, @@ -76,6 +77,7 @@ export function useSettingsViewOrchestration({ appSettings, openAppIconById, onUpdateAppSettings, + onToggleAutomaticAppUpdateChecks, onRunDoctor, onRunCodexUpdate, onUpdateWorkspaceSettings, @@ -213,7 +215,7 @@ export function useSettingsViewOrchestration({ return { aboutSectionProps: { appSettings, - onUpdateAppSettings, + onToggleAutomaticAppUpdateChecks, }, projectsSectionProps, environmentsSectionProps, diff --git a/src/features/update/hooks/useUpdater.ts b/src/features/update/hooks/useUpdater.ts index 0e382b26e..9b8b7265f 100644 --- a/src/features/update/hooks/useUpdater.ts +++ b/src/features/update/hooks/useUpdater.ts @@ -71,6 +71,7 @@ export function useUpdater({ null, ); const updateRef = useRef(null); + const hasAttemptedAutoCheckRef = useRef(false); const postUpdateFetchGenerationRef = useRef(0); const latestTimeoutRef = useRef(null); const latestToastDurationMs = 2000; @@ -213,6 +214,10 @@ export function useUpdater({ if (!enabled || !autoCheckOnMount || import.meta.env.DEV || !isTauri()) { return; } + if (hasAttemptedAutoCheckRef.current) { + return; + } + hasAttemptedAutoCheckRef.current = true; void checkForUpdates(); }, [autoCheckOnMount, checkForUpdates, enabled]);