From e83e396c096158fe002e2c4fe8989b8e3af3b9dd Mon Sep 17 00:00:00 2001 From: Christian Byrne Date: Tue, 17 Feb 2026 11:39:07 -0800 Subject: [PATCH] feat: gate node replacement loading on server feature flag (#8750) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Gates the node replacement store's `load()` call behind the `node_replacements` server feature flag, so the frontend only calls `/api/node_replacements` when the backend advertises support. ## Changes - Added `NODE_REPLACEMENTS = 'node_replacements'` to `ServerFeatureFlag` enum - Added `nodeReplacementsEnabled` getter to `useFeatureFlags()` - Added `api.serverSupportsFeature('node_replacements')` guard in `useNodeReplacementStore.load()` ## Context Without this guard, the frontend would attempt to fetch node replacements from backends that don't support the endpoint, causing 404 errors. Companion backend PR: https://github.com/Comfy-Org/ComfyUI/pull/12362 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-8750-feat-gate-node-replacement-loading-on-server-feature-flag-3026d73d365081ec9246d77ad88f5bdc) by [Unito](https://www.unito.io) --------- Co-authored-by: Jin Yi Co-authored-by: Alexander Brown Co-authored-by: Claude --- src/composables/useFeatureFlags.ts | 6 ++++- .../nodeReplacementStore.test.ts | 26 ++++++++++++++++++- .../nodeReplacement/nodeReplacementStore.ts | 5 ++++ 3 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/composables/useFeatureFlags.ts b/src/composables/useFeatureFlags.ts index 070419008c..19fea30707 100644 --- a/src/composables/useFeatureFlags.ts +++ b/src/composables/useFeatureFlags.ts @@ -20,7 +20,8 @@ export enum ServerFeatureFlag { ONBOARDING_SURVEY_ENABLED = 'onboarding_survey_enabled', LINEAR_TOGGLE_ENABLED = 'linear_toggle_enabled', TEAM_WORKSPACES_ENABLED = 'team_workspaces_enabled', - USER_SECRETS_ENABLED = 'user_secrets_enabled' + USER_SECRETS_ENABLED = 'user_secrets_enabled', + NODE_REPLACEMENTS = 'node_replacements' } /** @@ -96,6 +97,9 @@ export function useFeatureFlags() { remoteConfig.value.user_secrets_enabled ?? api.getServerFeature(ServerFeatureFlag.USER_SECRETS_ENABLED, false) ) + }, + get nodeReplacementsEnabled() { + return api.getServerFeature(ServerFeatureFlag.NODE_REPLACEMENTS, false) } }) diff --git a/src/platform/nodeReplacement/nodeReplacementStore.test.ts b/src/platform/nodeReplacement/nodeReplacementStore.test.ts index a661d86ee8..a8550ecb78 100644 --- a/src/platform/nodeReplacement/nodeReplacementStore.test.ts +++ b/src/platform/nodeReplacement/nodeReplacementStore.test.ts @@ -15,6 +15,18 @@ vi.mock('./nodeReplacementService', () => ({ fetchNodeReplacements: vi.fn() })) +const mockNodeReplacementsEnabled = vi.hoisted(() => ({ value: true })) + +vi.mock('@/composables/useFeatureFlags', () => ({ + useFeatureFlags: vi.fn(() => ({ + flags: { + get nodeReplacementsEnabled() { + return mockNodeReplacementsEnabled.value + } + } + })) +})) + function mockSettingStore(enabled: boolean) { vi.mocked(useSettingStore, { partial: true }).mockReturnValue({ get: vi.fn().mockImplementation((key: string) => { @@ -27,9 +39,10 @@ function mockSettingStore(enabled: boolean) { }) } -function createStore(enabled = true) { +function createStore(enabled = true, featureEnabled = true) { setActivePinia(createPinia()) mockSettingStore(enabled) + mockNodeReplacementsEnabled.value = featureEnabled return useNodeReplacementStore() } @@ -38,6 +51,7 @@ describe('useNodeReplacementStore', () => { beforeEach(() => { vi.clearAllMocks() + mockNodeReplacementsEnabled.value = true store = createStore(true) }) @@ -257,5 +271,15 @@ describe('useNodeReplacementStore', () => { expect(fetchNodeReplacements).not.toHaveBeenCalled() expect(store.isLoaded).toBe(false) }) + + it('should not call API when server feature flag is disabled', async () => { + vi.mocked(fetchNodeReplacements).mockResolvedValue(mockReplacements) + store = createStore(true, false) + + await store.load() + + expect(fetchNodeReplacements).not.toHaveBeenCalled() + expect(store.isLoaded).toBe(false) + }) }) }) diff --git a/src/platform/nodeReplacement/nodeReplacementStore.ts b/src/platform/nodeReplacement/nodeReplacementStore.ts index 2f749947f2..713bb687f7 100644 --- a/src/platform/nodeReplacement/nodeReplacementStore.ts +++ b/src/platform/nodeReplacement/nodeReplacementStore.ts @@ -3,6 +3,7 @@ import type { NodeReplacement, NodeReplacementResponse } from './types' import { defineStore } from 'pinia' import { computed, ref } from 'vue' +import { useFeatureFlags } from '@/composables/useFeatureFlags' import { useSettingStore } from '@/platform/settings/settingStore' import { fetchNodeReplacements } from './nodeReplacementService' @@ -14,8 +15,12 @@ export const useNodeReplacementStore = defineStore('nodeReplacement', () => { settingStore.get('Comfy.NodeReplacement.Enabled') ) + const { flags } = useFeatureFlags() + async function load() { if (!isEnabled.value || isLoaded.value) return + if (!flags.nodeReplacementsEnabled) return + try { replacements.value = await fetchNodeReplacements() isLoaded.value = true