refactor: parallelize bootstrap and simplify lifecycle with VueUse (#8307)

## Summary

Refactors bootstrap and lifecycle management to parallelize
initialization, use Vue best practices, and fix a logout state bug.

## Changes

### Bootstrap Store (`bootstrapStore.ts`)
- Extract early bootstrap logic into a dedicated store using
`useAsyncState`
- Parallelize settings, i18n, and workflow sync loading (previously
sequential)
- Handle multi-user login scenarios by deferring settings/workflows
until authenticated

### GraphCanvas Refactoring
- Move non-DOM composables (`useGlobalLitegraph`, `useCopy`, `usePaste`,
etc.) to script setup level for earlier initialization
- Move `watch` and `whenever` declarations outside `onMounted` (Vue best
practice)
- Use `until()` from VueUse to await bootstrap store readiness instead
of direct async calls

### GraphView Simplification
- Replace manual `addEventListener`/`removeEventListener` with
`useEventListener`
- Replace `setInterval` with `useIntervalFn` for automatic cleanup
- Move core command registration to script setup level

### Bug Fix
Using `router.push()` for logout caused `isSettingsReady` to persist as
`true`, making new users inherit the previous user's cached settings.
Reverted to `window.location.reload()` with TODOs for future store reset
implementation.

## Testing

- Verified login/logout cycle clears settings correctly
- Verified bootstrap sequence completes without errors

---------

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Alexander Brown
2026-01-27 12:50:13 -08:00
committed by GitHub
parent 788f50834c
commit 14369c08a3
19 changed files with 422 additions and 187 deletions

View File

@@ -24,7 +24,7 @@
</template>
<script setup lang="ts">
import { useEventListener } from '@vueuse/core'
import { useEventListener, useIntervalFn } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import type { ToastMessageOptions } from 'primevue/toast'
import { useToast } from 'primevue/usetoast'
@@ -79,7 +79,6 @@ import { useServerConfigStore } from '@/stores/serverConfigStore'
import { useBottomPanelStore } from '@/stores/workspace/bottomPanelStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import { useSidebarTabStore } from '@/stores/workspace/sidebarTabStore'
import { useWorkspaceStore } from '@/stores/workspaceStore'
import { electronAPI, isElectron } from '@/utils/envUtil'
import LinearView from '@/views/LinearView.vue'
import ManagerProgressToast from '@/workbench/extensions/manager/components/ManagerProgressToast.vue'
@@ -102,9 +101,6 @@ const { linearMode } = storeToRefs(useCanvasStore())
const telemetry = useTelemetry()
const firebaseAuthStore = useFirebaseAuthStore()
let hasTrackedLogin = false
let visibilityListener: (() => void) | null = null
let tabCountInterval: number | null = null
let tabCountChannel: BroadcastChannel | null = null
watch(
() => colorPaletteStore.completedActivePalette,
@@ -198,15 +194,12 @@ watchEffect(() => {
queueStore.maxHistoryItems = settingStore.get('Comfy.Queue.MaxHistoryItems')
})
const init = () => {
const coreCommands = useCoreCommands()
useCommandStore().registerCommands(coreCommands)
useMenuItemStore().registerCoreMenuCommands()
useKeybindingService().registerCoreKeybindings()
useSidebarTabStore().registerCoreSidebarTabs()
useBottomPanelStore().registerCoreBottomPanelTabs()
app.extensionManager = useWorkspaceStore()
}
const coreCommands = useCoreCommands()
useCommandStore().registerCommands(coreCommands)
useMenuItemStore().registerCoreMenuCommands()
useKeybindingService().registerCoreKeybindings()
useSidebarTabStore().registerCoreSidebarTabs()
useBottomPanelStore().registerCoreBottomPanelTabs()
const queuePendingTaskCountStore = useQueuePendingTaskCountStore()
const sidebarTabStore = useSidebarTabStore()
@@ -274,15 +267,15 @@ if (isCloud) {
)
}
useEventListener(api, 'status', onStatus)
useEventListener(api, 'execution_success', onExecutionSuccess)
useEventListener(api, 'reconnecting', onReconnecting)
useEventListener(api, 'reconnected', onReconnected)
onMounted(() => {
api.addEventListener('status', onStatus)
api.addEventListener('execution_success', onExecutionSuccess)
api.addEventListener('reconnecting', onReconnecting)
api.addEventListener('reconnected', onReconnected)
executionStore.bindExecutionEvents()
try {
init()
// Relocate the legacy menu container to the graph canvas container so it is below other elements
graphCanvasContainerRef.value?.prepend(app.ui.menuContainer)
} catch (e) {
@@ -291,27 +284,7 @@ onMounted(() => {
})
onBeforeUnmount(() => {
api.removeEventListener('status', onStatus)
api.removeEventListener('execution_success', onExecutionSuccess)
api.removeEventListener('reconnecting', onReconnecting)
api.removeEventListener('reconnected', onReconnected)
executionStore.unbindExecutionEvents()
// Clean up page visibility listener
if (visibilityListener) {
document.removeEventListener('visibilitychange', visibilityListener)
visibilityListener = null
}
// Clean up tab count tracking
if (tabCountInterval) {
window.clearInterval(tabCountInterval)
tabCountInterval = null
}
if (tabCountChannel) {
tabCountChannel.close()
tabCountChannel = null
}
})
useEventListener(window, 'keydown', useKeybindingService().keybindHandler)
@@ -337,18 +310,17 @@ const onGraphReady = () => {
}
// Set up page visibility tracking (cloud only)
if (isCloud && telemetry && !visibilityListener) {
visibilityListener = () => {
if (isCloud && telemetry) {
useEventListener(document, 'visibilitychange', () => {
telemetry.trackPageVisibilityChanged({
visibility_state: document.visibilityState as 'visible' | 'hidden'
})
}
document.addEventListener('visibilitychange', visibilityListener)
})
}
// Set up tab count tracking (cloud only)
if (isCloud && telemetry && !tabCountInterval) {
tabCountChannel = new BroadcastChannel('comfyui-tab-count')
if (isCloud && telemetry) {
const tabCountChannel = new BroadcastChannel('comfyui-tab-count')
const activeTabs = new Map<string, number>()
const currentTabId = crypto.randomUUID()
@@ -363,7 +335,7 @@ const onGraphReady = () => {
}
// 5-minute heartbeat interval
tabCountInterval = window.setInterval(() => {
useIntervalFn(() => {
const now = Date.now()
// Clean up stale tabs (no heartbeat for 45 seconds)
@@ -374,7 +346,7 @@ const onGraphReady = () => {
})
// Broadcast our heartbeat
tabCountChannel?.postMessage({
tabCountChannel.postMessage({
type: 'heartbeat',
tabId: currentTabId
})