Compare commits

...

2 Commits

Author SHA1 Message Date
Jedrzej Kosinski
df7eaab505 fix: wait for server feature flags before fetching releases
Gates release fetching in releaseStore.initialize() on the server feature flags arriving (non-empty), with a 3s timeout fallback, so a host that set show_version_updates=false isn't briefly treated as enabled on first load before negotiation completes. Also replaces the change-detector setting test with behavioral coverage.

Amp-Thread-ID: https://ampcode.com/threads/T-019f0347-dc00-723a-a4d2-558539329035
Co-authored-by: Amp <amp@ampcode.com>
2026-06-26 03:23:09 -07:00
Jedrzej Kosinski
9198f13636 feat: drive ShowVersionUpdates default from server feature flag
The Comfy.Notification.ShowVersionUpdates default now reads the show_version_updates server feature flag (defaulting to true), letting launchers like ComfyUI Desktop change whether new-release notifications show by default. The user setting still overrides it.

Amp-Thread-ID: https://ampcode.com/threads/T-019f0347-dc00-723a-a4d2-558539329035
Co-authored-by: Amp <amp@ampcode.com>
2026-06-26 02:55:33 -07:00
5 changed files with 66 additions and 5 deletions

View File

@@ -30,7 +30,8 @@ export enum ServerFeatureFlag {
COMFYHUB_PROFILE_GATE_ENABLED = 'comfyhub_profile_gate_enabled',
SHOW_SIGNIN_BUTTON = 'show_signin_button',
UNIFIED_CLOUD_AUTH = 'unified_cloud_auth',
SIGNUP_TURNSTILE = 'signup_turnstile'
SIGNUP_TURNSTILE = 'signup_turnstile',
SHOW_VERSION_UPDATES = 'show_version_updates'
}
/**

View File

@@ -0,0 +1,33 @@
import { afterEach, describe, expect, it, vi } from 'vitest'
import { api } from '@/scripts/api'
import { CORE_SETTINGS } from './coreSettings'
function getDefault(id: string) {
const setting = CORE_SETTINGS.find((s) => s.id === id)
if (!setting) throw new Error(`Setting ${id} not found`)
const { defaultValue } = setting
return typeof defaultValue === 'function' ? defaultValue() : defaultValue
}
describe('Comfy.Notification.ShowVersionUpdates default', () => {
afterEach(() => {
vi.restoreAllMocks()
})
it('resolves to the show_version_updates server flag value', () => {
vi.spyOn(api, 'getServerFeature').mockReturnValue(true)
expect(getDefault('Comfy.Notification.ShowVersionUpdates')).toBe(true)
vi.spyOn(api, 'getServerFeature').mockReturnValue(false)
expect(getDefault('Comfy.Notification.ShowVersionUpdates')).toBe(false)
})
it('falls back to true when the server flag is unset', () => {
vi.spyOn(api, 'getServerFeature').mockImplementation(
(_name, fallback) => fallback
)
expect(getDefault('Comfy.Notification.ShowVersionUpdates')).toBe(true)
})
})

View File

@@ -6,6 +6,8 @@ import {
import { isCloud, isDesktop, isNightly } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import type { SettingParams } from '@/platform/settings/types'
import { ServerFeatureFlag } from '@/composables/useFeatureFlags'
import { api } from '@/scripts/api'
import type { ColorPalettes } from '@/schemas/colorPaletteSchema'
import type { Keybinding } from '@/platform/keybindings/types'
import { NodeBadgeMode } from '@/types/nodeSource'
@@ -484,7 +486,8 @@ export const CORE_SETTINGS: SettingParams[] = [
name: 'Show version updates',
tooltip: 'Show updates for new models, and major new features.',
type: 'boolean',
defaultValue: true
defaultValue: () =>
api.getServerFeature(ServerFeatureFlag.SHOW_VERSION_UPDATES, true)
},
{
id: 'Comfy.ConfirmClear',

View File

@@ -40,6 +40,14 @@ vi.mock('@/platform/updates/common/releaseService', () => {
}
})
vi.mock('@/scripts/api', () => ({
api: {
// Non-empty => feature flags already received, so fetchReleases' gate
// resolves immediately instead of waiting on the websocket.
serverFeatureFlags: ref<Record<string, unknown>>({ ready: true })
}
}))
vi.mock('@/platform/settings/settingStore', () => {
const get = vi.fn((key: string) => {
if (key === 'Comfy.Notification.ShowVersionUpdates') return true
@@ -95,7 +103,12 @@ vi.mock('@/stores/systemStatsStore', () => {
}
})
vi.mock('@vueuse/core', () => ({
until: vi.fn(() => Promise.resolve()),
// until() is awaited directly in some call sites and chained as
// until(...).toBe(...) in others; support both shapes.
until: vi.fn(() => {
const resolved = Promise.resolve()
return Object.assign(resolved, { toBe: vi.fn(() => Promise.resolve()) })
}),
useStorage: vi.fn(() => ({ value: {} })),
createSharedComposable: vi.fn((fn) => fn)
}))
@@ -442,11 +455,11 @@ describe('useReleaseStore', () => {
vi.mocked(releaseService.getReleases).mockReturnValue(promise)
const initPromise = store.initialize()
const fetchPromise = store.fetchReleases()
expect(store.isLoading).toBe(true)
resolvePromise!([mockRelease])
await initPromise
await fetchPromise
expect(store.isLoading).toBe(false)
})

View File

@@ -5,6 +5,7 @@ import { computed, ref } from 'vue'
import { isCloud, isDesktop } from '@/platform/distribution/types'
import { useSettingStore } from '@/platform/settings/settingStore'
import { api } from '@/scripts/api'
import { useSystemStatsStore } from '@/stores/systemStatsStore'
import { stringToLocale } from '@/utils/formatUtil'
@@ -294,6 +295,16 @@ export const useReleaseStore = defineStore('release', () => {
// Initialize store
async function initialize(): Promise<void> {
// showVersionUpdates' default is seeded by the show_version_updates server
// feature flag, which arrives over the websocket shortly after connect. Wait
// for the flags (non-empty once received) so a host that disabled updates
// isn't briefly treated as enabled on first load; fall back after a timeout
// so a missing/late flag message can't block fetching forever.
if (!isCloud) {
await until(
() => Object.keys(api.serverFeatureFlags.value).length > 0
).toBe(true, { timeout: 3000 })
}
await fetchReleases()
}