Display a separate indicator for pending jobs in app mode (#10382)

Previously, a pending, but not yet running job was displayed as an empty
skeleton. It is instead updated to display an animated `lucide--loader`
icon.

<img width="1544" height="1347" alt="image"
src="https://github.com/user-attachments/assets/4f82185c-97dc-44af-8ea1-a012fb992fe2"
/>

As this shifts the activeQueueItem component to display even when only
the single pending item is in the queue, the count badge is also update
to not display when this is the case.

┆Issue is synchronized with this [Notion
page](https://www.notion.so/PR-10382-Display-a-separate-indicator-for-pending-jobs-in-app-mode-32a6d73d36508189b3a9ff4b84993a98)
by [Unito](https://www.unito.io)
This commit is contained in:
AustinMroz
2026-03-23 09:09:09 -07:00
committed by GitHub
parent b33c8c2d30
commit bd322314bc
6 changed files with 28 additions and 16 deletions

View File

@@ -14,6 +14,11 @@ const meta: Meta<typeof Loader> = {
control: 'select',
options: ['sm', 'md', 'lg'],
description: 'Spinner size: sm (16px), md (32px), lg (48px)'
},
variant: {
control: 'select',
options: ['loader', 'loader-circle'],
description: 'The type of loader displayed'
}
}
}

View File

@@ -307,7 +307,7 @@ useEventListener(document.body, 'keydown', (e: KeyboardEvent) => {
class="flex h-15 shrink-0 items-start gap-0.5"
>
<OutputHistoryActiveQueueItem
v-if="queueCount > 1"
v-if="queueCount > 1 || queueStore.pendingTasks.length"
class="mr-3"
:queue-count="queueCount"
/>

View File

@@ -1,11 +1,13 @@
import { createTestingPinia } from '@pinia/testing'
import { shallowMount } from '@vue/test-utils'
import { setActivePinia } from 'pinia'
import { describe, expect, it, vi } from 'vitest'
import { createI18n } from 'vue-i18n'
import OutputHistoryActiveQueueItem from './OutputHistoryActiveQueueItem.vue'
vi.mock('vue-i18n', () => ({
useI18n: () => ({ t: (key: string) => key })
}))
const i18n = createI18n({ legacy: false, locale: 'en' })
setActivePinia(createTestingPinia({ stubActions: false }))
vi.mock('@/stores/commandStore', () => ({
useCommandStore: () => ({
@@ -15,16 +17,16 @@ vi.mock('@/stores/commandStore', () => ({
function mountComponent(queueCount: number) {
return shallowMount(OutputHistoryActiveQueueItem, {
props: { queueCount }
props: { queueCount },
global: { plugins: [i18n] }
})
}
describe('OutputHistoryActiveQueueItem', () => {
it('shows badge when queueCount is 1', () => {
it('hides badge when queueCount is 1', () => {
const wrapper = mountComponent(1)
const badge = wrapper.find('[aria-hidden="true"]')
expect(badge.exists()).toBe(true)
expect(badge.text()).toBe('1')
expect(badge.exists()).toBe(false)
})
it('shows badge with correct count when queueCount is 3', () => {

View File

@@ -5,6 +5,7 @@ import Loader from '@/components/loader/Loader.vue'
import Popover from '@/components/ui/Popover.vue'
import Button from '@/components/ui/button/Button.vue'
import { useCommandStore } from '@/stores/commandStore'
import { useQueueStore } from '@/stores/queueStore'
const { queueCount } = defineProps<{
queueCount: number
@@ -12,6 +13,7 @@ const { queueCount } = defineProps<{
const { t } = useI18n()
const commandStore = useCommandStore()
const queueStore = useQueueStore()
function clearQueue(close: () => void) {
void commandStore.execute('Comfy.ClearPendingTasks')
@@ -32,7 +34,13 @@ function clearQueue(close: () => void) {
size="unset"
class="flex size-10 items-center justify-center rounded-sm bg-secondary-background"
>
<Loader size="sm" class="text-muted-foreground" />
<Loader
:variant="
queueStore.runningTasks.length ? 'loader-circle' : 'loader'
"
size="sm"
class="text-muted-foreground"
/>
</Button>
</template>
<template #default="{ close }">
@@ -48,7 +56,7 @@ function clearQueue(close: () => void) {
</template>
</Popover>
<div
v-if="queueCount > 0"
v-if="queueCount > 1"
aria-hidden="true"
class="absolute top-0 right-0 flex h-4 min-w-4 items-center justify-center rounded-full bg-primary-background text-xs text-text-primary"
v-text="queueCount"

View File

@@ -438,12 +438,12 @@ describe(useOutputHistory, () => {
expect(mayBeActiveWorkflowPending.value).toBe(true)
})
it('returns true when a pending task matches the active workflow', () => {
it('returns false when only pending tasks exist', () => {
pendingTasksRef.value = [{ jobId: 'job-1' }]
jobIdToPathRef.value = new Map([['job-1', 'workflows/test.json']])
const { mayBeActiveWorkflowPending } = useOutputHistory()
expect(mayBeActiveWorkflowPending.value).toBe(true)
expect(mayBeActiveWorkflowPending.value).toBe(false)
})
it('returns false when tasks belong to another workflow', () => {

View File

@@ -43,10 +43,7 @@ export function useOutputHistory(): {
function hasActiveWorkflowJobs(): boolean {
if (!workflowStore.activeWorkflow?.path) return false
return (
queueStore.runningTasks.some(matchesActiveWorkflow) ||
queueStore.pendingTasks.some(matchesActiveWorkflow)
)
return queueStore.runningTasks.some(matchesActiveWorkflow)
}
// True when there are queued/running jobs for the active workflow but no