fix: resolve cloud/1.41 unit test regressions (#10269)

## Summary

Fixes three cloud/1.41 test regressions by restoring the intended legacy
topbar mutation scheduling behavior, re-enabling multi-output test
mocking in assetsStore tests, and backporting execution ID locator
caching in executionStore.

## Changes

- **What**: Reintroduced RAF-coalesced legacy topbar content checks in
`TopMenuSection`, restored `mockOutputOverrides` handling in
`assetsStore.test.ts`, and added cached execution-id-to-locator
resolution in `executionStore` with cache resets at execution
boundaries.

## Review Focus

Confirm the cache lifecycle boundaries in `executionStore`
(`handleExecutionStart` and `resetExecutionState`) and that the
`TopMenuSection` mutation observer now matches the coalescing contract
expected by unit tests.

Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Alexander Brown
2026-03-18 13:20:55 -07:00
committed by GitHub
parent 6bb46d688f
commit 5f99f7bdba
3 changed files with 52 additions and 3 deletions

View File

@@ -117,7 +117,7 @@
<script setup lang="ts">
import { useLocalStorage, useMutationObserver } from '@vueuse/core'
import { storeToRefs } from 'pinia'
import { computed, onMounted, ref } from 'vue'
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import ComfyActionbar from '@/components/actionbar/ComfyActionbar.vue'
@@ -265,6 +265,7 @@ const rightSidePanelTooltipConfig = computed(() =>
// Maintain support for legacy topbar elements attached by custom scripts
const legacyCommandsContainerRef = ref<HTMLElement>()
const hasLegacyContent = ref(false)
let legacyContentCheckRafId: number | null = null
function checkLegacyContent() {
const el = legacyCommandsContainerRef.value
@@ -277,7 +278,16 @@ function checkLegacyContent() {
el.querySelector(':scope > * > *:not(:empty)') !== null
}
useMutationObserver(legacyCommandsContainerRef, checkLegacyContent, {
function scheduleLegacyContentCheck() {
if (legacyContentCheckRafId !== null) return
legacyContentCheckRafId = requestAnimationFrame(() => {
legacyContentCheckRafId = null
checkLegacyContent()
})
}
useMutationObserver(legacyCommandsContainerRef, scheduleLegacyContentCheck, {
childList: true,
subtree: true,
characterData: true
@@ -291,6 +301,13 @@ onMounted(() => {
}
})
onBeforeUnmount(() => {
if (legacyContentCheckRafId === null) return
cancelAnimationFrame(legacyContentCheckRafId)
legacyContentCheckRafId = null
})
const openCustomNodeManager = async () => {
try {
await managerState.openManager({

View File

@@ -111,6 +111,18 @@ vi.mock('@/stores/queueStore', () => ({
constructor(public job: JobListItem) {
this.jobId = job.id
this.outputsCount = job.outputs_count ?? null
if (mockOutputOverrides.value) {
this.flatOutputs = mockOutputOverrides.value
const previewable = mockOutputOverrides.value.filter(
(output) => output.supportsPreview
)
this.previewOutput =
previewable.findLast((output) => output.type === 'output') ??
previewable.at(-1)
return
}
const preview = job.preview_output
const isPreviewable =
!!preview?.filename && PREVIEWABLE_MEDIA_TYPES.has(preview.mediaType)

View File

@@ -80,6 +80,24 @@ export const useExecutionStore = defineStore('execution', () => {
const initializingJobIds = ref<Set<string>>(new Set())
/**
* Cache executionIdToNodeLocatorId lookups to avoid repeated graph
* traversals while progress updates stream for the same execution IDs.
*/
const executionIdToLocatorCache = new Map<string, NodeLocatorId | undefined>()
function cachedExecutionIdToLocator(
executionId: string
): NodeLocatorId | undefined {
if (executionIdToLocatorCache.has(executionId)) {
return executionIdToLocatorCache.get(executionId)
}
const locatorId = executionIdToNodeLocatorId(app.rootGraph, executionId)
executionIdToLocatorCache.set(executionId, locatorId)
return locatorId
}
const mergeExecutionProgressStates = (
currentState: NodeProgressState | undefined,
newState: NodeProgressState
@@ -119,7 +137,7 @@ export const useExecutionStore = defineStore('execution', () => {
const parts = String(state.display_node_id).split(':')
for (let i = 0; i < parts.length; i++) {
const executionId = parts.slice(0, i + 1).join(':')
const locatorId = executionIdToNodeLocatorId(app.rootGraph, executionId)
const locatorId = cachedExecutionIdToLocator(executionId)
if (!locatorId) continue
result[locatorId] = mergeExecutionProgressStates(
@@ -227,6 +245,7 @@ export const useExecutionStore = defineStore('execution', () => {
}
function handleExecutionStart(e: CustomEvent<ExecutionStartWsMessage>) {
executionIdToLocatorCache.clear()
executionErrorStore.clearAllErrors()
activeJobId.value = e.detail.prompt_id
queuedJobs.value[activeJobId.value] ??= { nodes: {} }
@@ -473,6 +492,7 @@ export const useExecutionStore = defineStore('execution', () => {
* Reset execution-related state after a run completes or is stopped.
*/
function resetExecutionState(jobIdParam?: string | null) {
executionIdToLocatorCache.clear()
nodeProgressStates.value = {}
const jobId = jobIdParam ?? activeJobId.value ?? null
if (jobId) {