diff --git a/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx b/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx index e5fdacea79..598b705347 100644 --- a/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx +++ b/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx @@ -112,12 +112,14 @@ const GeneralSection: React.FC = ({ // falling back to the backend version const currentVersion = appVersion ?? config?.appVersion ?? null; - // Check for updates on mount + // Check for updates on mount — skipped when the update UI is hidden (SaaS + // build, managed-disabled desktop) so no external update call ever fires. useEffect(() => { + if (hideUpdateSection) return; if (currentVersion) { checkForUpdate(); } - }, [currentVersion, config?.machineType]); + }, [currentVersion, config?.machineType, hideUpdateSection]); const checkForUpdate = async () => { if (!currentVersion) return; diff --git a/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx b/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx index abe37d10b8..98c4455ce0 100644 --- a/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx +++ b/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"; import CoreGeneralSection from "@core/components/shared/config/configSections/GeneralSection"; import { DefaultAppSettings } from "@app/components/shared/config/configSections/DefaultAppSettings"; import { useDesktopInstall } from "@app/hooks/useDesktopInstall"; +import { useSaaSMode } from "@app/hooks/useSaaSMode"; import { desktopUpdateService, type UpdateMode, @@ -22,6 +23,9 @@ import { const GeneralSection: React.FC = () => { const { t } = useTranslation(); const install = useDesktopInstall(); + // In SaaS connection mode the cloud owns app versioning — hide the update + // section (which also stops the core auto-check from firing). + const isSaaSMode = useSaaSMode(); const [updateModeInfo, setUpdateModeInfo] = useState({ mode: "prompt", locked: false, @@ -93,7 +97,8 @@ const GeneralSection: React.FC = () => { )} ({ invoke: (cmd: string, args?: unknown) => invokeMock(cmd, args), @@ -36,6 +37,12 @@ vi.mock("@app/services/desktopUpdateService", () => ({ }, })); +vi.mock("@app/services/connectionModeService", () => ({ + connectionModeService: { + getCurrentMode: () => getCurrentModeMock(), + }, +})); + import { useDesktopUpdatePopup } from "@app/hooks/useDesktopUpdatePopup"; /** Flush pending microtasks so awaited promises settle. */ @@ -74,8 +81,11 @@ describe("useDesktopUpdatePopup — auto mode", () => { getUpdateModeMock.mockReset(); canInstallUpdatesMock.mockReset(); getUpdateSummaryMock.mockReset(); + getCurrentModeMock.mockReset(); - // Defaults: auto mode, update available, install permitted. + // Defaults: local (non-SaaS) connection, auto mode, update available, + // install permitted. + getCurrentModeMock.mockResolvedValue("local"); getUpdateModeMock.mockResolvedValue("auto"); getVersionMock.mockResolvedValue("1.0.0"); getUpdateSummaryMock.mockResolvedValue({ latest_version: "2.0.0" }); @@ -189,4 +199,18 @@ describe("useDesktopUpdatePopup — auto mode", () => { expect(invocations).toContain("download_and_install_update"); expect(invocations).toContain("restart_app"); }); + + it("skips the update check entirely in SaaS connection mode", async () => { + // In SaaS mode the cloud owns versioning — the self-hosted update check + // must never run: no mode lookup, no external summary fetch, no install. + getCurrentModeMock.mockResolvedValue("saas"); + + await runStartup(); + + expect(getUpdateModeMock).not.toHaveBeenCalled(); + expect(getUpdateSummaryMock).not.toHaveBeenCalled(); + const invocations = invokeMock.mock.calls.map((c) => c[0]); + expect(invocations).not.toContain("download_and_install_update"); + expect(invocations).not.toContain("restart_app"); + }); }); diff --git a/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts b/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts index 6a2df77914..0cd0fcd003 100644 --- a/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts +++ b/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts @@ -6,6 +6,7 @@ import { desktopUpdateService, type CanInstallResult, } from "@app/services/desktopUpdateService"; +import { connectionModeService } from "@app/services/connectionModeService"; const SNOOZE_KEY = "stirling-pdf-updater:snoozedUntil"; const STARTUP_DELAY_MS = 15_000; @@ -72,6 +73,10 @@ export function useDesktopUpdatePopup() { hasChecked.current = true; const timer = setTimeout(async () => { + // In SaaS connection mode the cloud owns app versioning — the self-hosted + // update check + popup must never run (no external call, no modal). + if ((await connectionModeService.getCurrentMode()) === "saas") return; + let mode: Awaited> = "prompt"; try {