Skip to content

Commit 14369c0

Browse files
DrJKLampcode-com
andauthored
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>
1 parent 788f508 commit 14369c0

File tree

19 files changed

+423
-188
lines changed

19 files changed

+423
-188
lines changed

AGENTS.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -300,6 +300,12 @@ When referencing Comfy-Org repos:
300300
301301
Rules for agent-based coding tasks.
302302
303+
### Chrome DevTools MCP
304+
305+
When using `take_snapshot` to inspect dropdowns, listboxes, or other components with dynamic options:
306+
- Use `verbose: true` to see the full accessibility tree including list items
307+
- Non-verbose snapshots often omit nested options in comboboxes/listboxes
308+
303309
### Temporary Files
304310
305311
- Put planning documents under `/temp/plans/`

browser_tests/tests/versionMismatchWarnings.spec.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,10 @@ test.describe('Version Mismatch Warnings', () => {
3838

3939
test.beforeEach(async ({ comfyPage }) => {
4040
await comfyPage.setSetting('Comfy.UseNewMenu', 'Top')
41+
await comfyPage.setSetting(
42+
'Comfy.VersionCompatibility.DisableWarnings',
43+
false
44+
)
4145
})
4246

4347
test('should show version mismatch warnings when installed version lower than required', async ({

src/AGENTS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
- Clear public interfaces
1818
- Restrict extension access
1919
- Clean up subscriptions
20+
- Only expose state/actions that are used externally; keep internal state private
2021

2122
## General Guidelines
2223

src/App.vue

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010

1111
<script setup lang="ts">
1212
import { captureException } from '@sentry/vue'
13-
import { useEventListener } from '@vueuse/core'
1413
import BlockUI from 'primevue/blockui'
1514
import ProgressSpinner from 'primevue/progressspinner'
1615
import { computed, onMounted } from 'vue'
@@ -21,15 +20,13 @@ import { useWorkspaceStore } from '@/stores/workspaceStore'
2120
import { useConflictDetection } from '@/workbench/extensions/manager/composables/useConflictDetection'
2221
2322
import { electronAPI, isElectron } from './utils/envUtil'
23+
import { app } from '@/scripts/app'
2424
2525
const workspaceStore = useWorkspaceStore()
26+
app.extensionManager = useWorkspaceStore()
27+
2628
const conflictDetection = useConflictDetection()
2729
const isLoading = computed<boolean>(() => workspaceStore.spinner)
28-
const handleKey = (e: KeyboardEvent) => {
29-
workspaceStore.shiftDown = e.shiftKey
30-
}
31-
useEventListener(window, 'keydown', handleKey)
32-
useEventListener(window, 'keyup', handleKey)
3330
3431
const showContextMenu = (event: MouseEvent) => {
3532
const { target } = event

src/components/graph/GraphCanvas.vue

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@
9393
</template>
9494

9595
<script setup lang="ts">
96-
import { useEventListener, whenever } from '@vueuse/core'
96+
import { until, useEventListener } from '@vueuse/core'
9797
import {
9898
computed,
9999
nextTick,
@@ -129,7 +129,7 @@ import { useCopy } from '@/composables/useCopy'
129129
import { useGlobalLitegraph } from '@/composables/useGlobalLitegraph'
130130
import { usePaste } from '@/composables/usePaste'
131131
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
132-
import { mergeCustomNodesI18n, t } from '@/i18n'
132+
import { t } from '@/i18n'
133133
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
134134
import { useLitegraphSettings } from '@/platform/settings/composables/useLitegraphSettings'
135135
import { CORE_SETTINGS } from '@/platform/settings/constants/coreSettings'
@@ -144,12 +144,15 @@ import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteracti
144144
import TransformPane from '@/renderer/core/layout/transform/TransformPane.vue'
145145
import MiniMap from '@/renderer/extensions/minimap/MiniMap.vue'
146146
import LGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
147-
import { UnauthorizedError, api } from '@/scripts/api'
147+
import { UnauthorizedError } from '@/scripts/api'
148148
import { app as comfyApp } from '@/scripts/app'
149149
import { ChangeTracker } from '@/scripts/changeTracker'
150150
import { IS_CONTROL_WIDGET, updateControlWidgetLabel } from '@/scripts/widgets'
151151
import { useColorPaletteService } from '@/services/colorPaletteService'
152152
import { newUserService } from '@/services/newUserService'
153+
import { storeToRefs } from 'pinia'
154+
155+
import { useBootstrapStore } from '@/stores/bootstrapStore'
153156
import { useCommandStore } from '@/stores/commandStore'
154157
import { useExecutionStore } from '@/stores/executionStore'
155158
import { useNodeDefStore } from '@/stores/nodeDefStore'
@@ -175,11 +178,16 @@ const settingStore = useSettingStore()
175178
const nodeDefStore = useNodeDefStore()
176179
const workspaceStore = useWorkspaceStore()
177180
const canvasStore = useCanvasStore()
181+
const workflowStore = useWorkflowStore()
178182
const executionStore = useExecutionStore()
179183
const toastStore = useToastStore()
180184
const colorPaletteStore = useColorPaletteStore()
181185
const colorPaletteService = useColorPaletteService()
182186
const canvasInteractions = useCanvasInteractions()
187+
const bootstrapStore = useBootstrapStore()
188+
const { isI18nReady, i18nError } = storeToRefs(bootstrapStore)
189+
const { isReady: isSettingsReady, error: settingsError } =
190+
storeToRefs(settingStore)
183191
184192
const betaMenuEnabled = computed(
185193
() => settingStore.get('Comfy.UseNewMenu') !== 'Disabled'
@@ -386,15 +394,6 @@ useEventListener(
386394
{ passive: true }
387395
)
388396
389-
const loadCustomNodesI18n = async () => {
390-
try {
391-
const i18nData = await api.getCustomNodesI18n()
392-
mergeCustomNodesI18n(i18nData)
393-
} catch (error) {
394-
console.error('Failed to load custom nodes i18n', error)
395-
}
396-
}
397-
398397
const comfyAppReady = ref(false)
399398
const workflowPersistence = useWorkflowPersistence()
400399
const { flags } = useFeatureFlags()
@@ -404,35 +403,72 @@ useCanvasDrop(canvasRef)
404403
useLitegraphSettings()
405404
useNodeBadge()
406405
407-
onMounted(async () => {
408-
useGlobalLitegraph()
409-
useContextMenuTranslation()
410-
useCopy()
411-
usePaste()
412-
useWorkflowAutoSave()
413-
useVueFeatureFlags()
406+
useGlobalLitegraph()
407+
useContextMenuTranslation()
408+
useCopy()
409+
usePaste()
410+
useWorkflowAutoSave()
414411
415-
comfyApp.vueAppReady = true
412+
// Start watching for locale change after the initial value is loaded.
413+
watch(
414+
() => settingStore.get('Comfy.Locale'),
415+
async (_newLocale, oldLocale) => {
416+
if (!oldLocale) return
417+
await Promise.all([
418+
until(() => isSettingsReady.value || !!settingsError.value).toBe(true),
419+
until(() => isI18nReady.value || !!i18nError.value).toBe(true)
420+
])
421+
if (settingsError.value || i18nError.value) {
422+
console.warn(
423+
'Somehow the Locale setting was changed while the settings or i18n had a setup error'
424+
)
425+
}
426+
await useCommandStore().execute('Comfy.RefreshNodeDefinitions')
427+
await useWorkflowService().reloadCurrentWorkflow()
428+
}
429+
)
430+
useEventListener(
431+
() => canvasStore.canvas?.canvas,
432+
'litegraph:set-graph',
433+
() => {
434+
workflowStore.updateActiveGraph()
435+
}
436+
)
416437
438+
onMounted(async () => {
439+
comfyApp.vueAppReady = true
417440
workspaceStore.spinner = true
418441
// ChangeTracker needs to be initialized before setup, as it will overwrite
419442
// some listeners of litegraph canvas.
420443
ChangeTracker.init()
421-
await loadCustomNodesI18n()
422-
try {
423-
await settingStore.loadSettingValues()
424-
} catch (error) {
425-
if (error instanceof UnauthorizedError) {
444+
445+
await until(() => isSettingsReady.value || !!settingsError.value).toBe(true)
446+
447+
if (settingsError.value) {
448+
if (settingsError.value instanceof UnauthorizedError) {
426449
localStorage.removeItem('Comfy.userId')
427450
localStorage.removeItem('Comfy.userName')
428451
window.location.reload()
429-
} else {
430-
throw error
452+
return
431453
}
454+
throw settingsError.value
432455
}
456+
457+
// Register core settings immediately after settings are ready
433458
CORE_SETTINGS.forEach(settingStore.addSetting)
434459
435-
await newUserService().initializeIfNewUser(settingStore)
460+
// Wait for both i18n and newUserService in parallel
461+
// (newUserService only needs settings, not i18n)
462+
await Promise.all([
463+
until(() => isI18nReady.value || !!i18nError.value).toBe(true),
464+
newUserService().initializeIfNewUser(settingStore)
465+
])
466+
if (i18nError.value) {
467+
console.warn(
468+
'[GraphCanvas] Failed to load custom nodes i18n:',
469+
i18nError.value
470+
)
471+
}
436472
437473
// @ts-expect-error fixme ts strict error
438474
await comfyApp.setup(canvasRef.value)
@@ -487,25 +523,6 @@ onMounted(async () => {
487523
const releaseStore = useReleaseStore()
488524
void releaseStore.initialize()
489525
490-
// Start watching for locale change after the initial value is loaded.
491-
watch(
492-
() => settingStore.get('Comfy.Locale'),
493-
async () => {
494-
await useCommandStore().execute('Comfy.RefreshNodeDefinitions')
495-
await useWorkflowService().reloadCurrentWorkflow()
496-
}
497-
)
498-
499-
whenever(
500-
() => useCanvasStore().canvas,
501-
(canvas) => {
502-
useEventListener(canvas.canvas, 'litegraph:set-graph', () => {
503-
useWorkflowStore().updateActiveGraph()
504-
})
505-
},
506-
{ immediate: true }
507-
)
508-
509526
emit('ready')
510527
})
511528

src/composables/node/useNodeBadge.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,9 @@ export const useNodeBadge = () => {
7171
}
7272

7373
onMounted(() => {
74+
if (extensionStore.isExtensionInstalled('Comfy.NodeBadge')) return
75+
76+
// TODO: Fix the composables and watchers being setup in onMounted
7477
const nodePricing = useNodePricing()
7578

7679
watch(

src/composables/useCoreCommands.test.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -167,25 +167,30 @@ describe('useCoreCommands', () => {
167167
return {
168168
get: vi.fn().mockReturnValue(getReturnValue),
169169
addSetting: vi.fn(),
170-
loadSettingValues: vi.fn(),
170+
load: vi.fn(),
171171
set: vi.fn(),
172172
exists: vi.fn(),
173173
getDefaultValue: vi.fn(),
174+
isReady: true,
175+
isLoading: false,
176+
error: undefined,
174177
settingValues: {},
175178
settingsById: {},
176179
$id: 'setting',
177180
$state: {
178181
settingValues: {},
179-
settingsById: {}
182+
settingsById: {},
183+
isReady: true,
184+
isLoading: false,
185+
error: undefined
180186
},
181187
$patch: vi.fn(),
182188
$reset: vi.fn(),
183189
$subscribe: vi.fn(),
184190
$onAction: vi.fn(),
185191
$dispose: vi.fn(),
186-
_customProperties: new Set(),
187-
_p: {}
188-
} as ReturnType<typeof useSettingStore>
192+
_customProperties: new Set()
193+
} satisfies ReturnType<typeof useSettingStore>
189194
}
190195

191196
beforeEach(() => {

src/composables/useCoreCommands.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,9 @@ import { useWorkflowTemplateSelectorDialog } from './useWorkflowTemplateSelector
6767
import { useMaskEditorStore } from '@/stores/maskEditorStore'
6868
import { useDialogStore } from '@/stores/dialogStore'
6969

70-
const { isActiveSubscription, showSubscriptionDialog } = useSubscription()
71-
7270
const moveSelectedNodesVersionAdded = '1.22.2'
7371
export function useCoreCommands(): ComfyCommand[] {
72+
const { isActiveSubscription, showSubscriptionDialog } = useSubscription()
7473
const workflowService = useWorkflowService()
7574
const workflowStore = useWorkflowStore()
7675
const dialogService = useDialogService()

src/main.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { VueFire, VueFireAuth } from 'vuefire'
1414
import { getFirebaseConfig } from '@/config/firebase'
1515
import '@/lib/litegraph/public/css/litegraph.css'
1616
import router from '@/router'
17+
import { useBootstrapStore } from '@/stores/bootstrapStore'
1718

1819
import App from './App.vue'
1920
// Intentionally relative import to ensure the CSS is loaded in the right order (after litegraph.css)
@@ -46,6 +47,7 @@ const firebaseApp = initializeApp(getFirebaseConfig())
4647

4748
const app = createApp(App)
4849
const pinia = createPinia()
50+
4951
Sentry.init({
5052
app,
5153
dsn: __SENTRY_DSN__,
@@ -91,4 +93,7 @@ app
9193
modules: [VueFireAuth()]
9294
})
9395

96+
const bootstrapStore = useBootstrapStore(pinia)
97+
void bootstrapStore.startStoreBootstrap()
98+
9499
app.mount('#vue-app')

src/platform/cloud/subscription/composables/useSubscription.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,10 @@ function useSubscriptionInternal() {
252252
if (loggedIn) {
253253
try {
254254
await fetchSubscriptionStatus()
255+
} catch (error) {
256+
// Network errors are expected during navigation/component unmount
257+
// and when offline - log for debugging but don't surface to user
258+
console.error('Failed to fetch subscription status:', error)
255259
} finally {
256260
isInitialized.value = true
257261
}

0 commit comments

Comments
 (0)