From ba100c4a0482607f5922b9b0a4262545f56f1ba7 Mon Sep 17 00:00:00 2001 From: Benjamin Lu Date: Fri, 7 Nov 2025 20:30:34 -0800 Subject: [PATCH] Hide browser tab star when autosave is enabled (#6568) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Hide "*" indicator in the browser tab title when autosave is enabled (Comfy.Workflow.AutoSave === 'after delay'). - Refactor: extract readable computed values (`shouldShowUnsavedIndicator`, `isActiveWorkflowModified`, `isActiveWorkflowPersisted`). - Aligns with workflow tab behavior; also hides while Shift is held (matches in-app tab logic). Files touched: - src/composables/useBrowserTabTitle.ts Validation: - Ran `pnpm lint:fix` and `pnpm typecheck` — both passed. Manual test suggestions: - With autosave set to 'after delay': modify a workflow → browser tab should not show `*`. - With autosave 'off': modify or open non-persisted workflow → browser tab shows `*`. - Hold Shift: indicator hidden while held (consistent with workflow tab). ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6568-Hide-browser-tab-star-when-autosave-is-enabled-refactor-title-logic-2a16d73d365081549906e9d1fed07a42) by [Unito](https://www.unito.io) --- src/composables/useBrowserTabTitle.ts | 26 ++++++- .../tests/composables/BrowserTabTitle.test.ts | 73 +++++++++++++++++-- 2 files changed, 87 insertions(+), 12 deletions(-) diff --git a/src/composables/useBrowserTabTitle.ts b/src/composables/useBrowserTabTitle.ts index 82b08d71a..3245da5dd 100644 --- a/src/composables/useBrowserTabTitle.ts +++ b/src/composables/useBrowserTabTitle.ts @@ -5,6 +5,7 @@ import { t } from '@/i18n' import { useSettingStore } from '@/platform/settings/settingStore' import { useWorkflowStore } from '@/platform/workflow/management/stores/workflowStore' import { useExecutionStore } from '@/stores/executionStore' +import { useWorkspaceStore } from '@/stores/workspaceStore' const DEFAULT_TITLE = 'ComfyUI' const TITLE_SUFFIX = ' - ComfyUI' @@ -13,6 +14,7 @@ export const useBrowserTabTitle = () => { const executionStore = useExecutionStore() const settingStore = useSettingStore() const workflowStore = useWorkflowStore() + const workspaceStore = useWorkspaceStore() const executionText = computed(() => executionStore.isIdle @@ -24,11 +26,27 @@ export const useBrowserTabTitle = () => { () => settingStore.get('Comfy.UseNewMenu') !== 'Disabled' ) + const isAutoSaveEnabled = computed( + () => settingStore.get('Comfy.Workflow.AutoSave') === 'after delay' + ) + + const isActiveWorkflowModified = computed( + () => !!workflowStore.activeWorkflow?.isModified + ) + const isActiveWorkflowPersisted = computed( + () => !!workflowStore.activeWorkflow?.isPersisted + ) + + const shouldShowUnsavedIndicator = computed(() => { + if (workspaceStore.shiftDown) return false + if (isAutoSaveEnabled.value) return false + if (!isActiveWorkflowPersisted.value) return true + if (isActiveWorkflowModified.value) return true + return false + }) + const isUnsavedText = computed(() => - workflowStore.activeWorkflow?.isModified || - !workflowStore.activeWorkflow?.isPersisted - ? ' *' - : '' + shouldShowUnsavedIndicator.value ? ' *' : '' ) const workflowNameText = computed(() => { const workflowName = workflowStore.activeWorkflow?.filename diff --git a/tests-ui/tests/composables/BrowserTabTitle.test.ts b/tests-ui/tests/composables/BrowserTabTitle.test.ts index 7b55ffc25..3c0cc623f 100644 --- a/tests-ui/tests/composables/BrowserTabTitle.test.ts +++ b/tests-ui/tests/composables/BrowserTabTitle.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import { nextTick, reactive } from 'vue' +import { effectScope, nextTick, reactive } from 'vue' +import type { EffectScope } from 'vue' import { useBrowserTabTitle } from '@/composables/useBrowserTabTitle' @@ -38,6 +39,14 @@ vi.mock('@/platform/workflow/management/stores/workflowStore', () => ({ useWorkflowStore: () => workflowStore })) +// Mock the workspace store +const workspaceStore = reactive({ + shiftDown: false +}) +vi.mock('@/stores/workspaceStore', () => ({ + useWorkspaceStore: () => workspaceStore +})) + describe('useBrowserTabTitle', () => { beforeEach(() => { // reset execution store @@ -51,14 +60,17 @@ describe('useBrowserTabTitle', () => { // reset setting and workflow stores ;(settingStore.get as any).mockReturnValue('Enabled') workflowStore.activeWorkflow = null + workspaceStore.shiftDown = false // reset document title document.title = '' }) it('sets default title when idle and no workflow', () => { - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) expect(document.title).toBe('ComfyUI') + scope.stop() }) it('sets workflow name as title when workflow exists and menu enabled', async () => { @@ -68,9 +80,11 @@ describe('useBrowserTabTitle', () => { isModified: false, isPersisted: true } - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('myFlow - ComfyUI') + scope.stop() }) it('adds asterisk for unsaved workflow', async () => { @@ -80,9 +94,44 @@ describe('useBrowserTabTitle', () => { isModified: true, isPersisted: true } - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('*myFlow - ComfyUI') + scope.stop() + }) + + it('hides asterisk when autosave is enabled', async () => { + ;(settingStore.get as any).mockImplementation((key: string) => { + if (key === 'Comfy.Workflow.AutoSave') return 'after delay' + if (key === 'Comfy.UseNewMenu') return 'Enabled' + return 'Enabled' + }) + workflowStore.activeWorkflow = { + filename: 'myFlow', + isModified: true, + isPersisted: true + } + useBrowserTabTitle() + await nextTick() + expect(document.title).toBe('myFlow - ComfyUI') + }) + + it('hides asterisk while Shift key is held', async () => { + ;(settingStore.get as any).mockImplementation((key: string) => { + if (key === 'Comfy.Workflow.AutoSave') return 'off' + if (key === 'Comfy.UseNewMenu') return 'Enabled' + return 'Enabled' + }) + workspaceStore.shiftDown = true + workflowStore.activeWorkflow = { + filename: 'myFlow', + isModified: true, + isPersisted: true + } + useBrowserTabTitle() + await nextTick() + expect(document.title).toBe('myFlow - ComfyUI') }) // Fails when run together with other tests. Suspect to be caused by leaked @@ -94,17 +143,21 @@ describe('useBrowserTabTitle', () => { isModified: false, isPersisted: true } - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('ComfyUI') + scope.stop() }) it('shows execution progress when not idle without workflow', async () => { executionStore.isIdle = false executionStore.executionProgress = 0.3 - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('[30%]ComfyUI') + scope.stop() }) it('shows node execution title when executing a node using nodeProgressStates', async () => { @@ -122,9 +175,11 @@ describe('useBrowserTabTitle', () => { } } } - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('[40%][50%] Foo') + scope.stop() }) it('shows multiple nodes running when multiple nodes are executing', async () => { @@ -140,8 +195,10 @@ describe('useBrowserTabTitle', () => { }, '2': { state: 'running', value: 8, max: 10, node: '2', prompt_id: 'test' } } - useBrowserTabTitle() + const scope: EffectScope = effectScope() + scope.run(() => useBrowserTabTitle()) await nextTick() expect(document.title).toBe('[40%][2 nodes running]') + scope.stop() }) })