Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
aa1c25f
feat: node-specific error tab, selection-aware grouping, and error ov…
jaeone94 Feb 18, 2026
4fbf89a
refactor: consolidate error logic and split errorGroups by scope
jaeone94 Feb 18, 2026
33f136b
fix: use allErrorGroups in expandFocusedErrorGroup to restore See Err…
jaeone94 Feb 18, 2026
c54b470
chore: remove unused useRightSidePanelStore import from app.ts
jaeone94 Feb 18, 2026
cc151d3
fix: remove redundant cn() in ErrorOverlay and fix prompt error clear…
jaeone94 Feb 18, 2026
f48ae46
fix: fix service-level error guard and null exception_type in message
jaeone94 Feb 18, 2026
e02d58d
fix: clear lastNodeErrors on execution start and guard node_id in all…
jaeone94 Feb 18, 2026
864b14b
Merge branch 'main' into feat/node-specific-error-tab
jaeone94 Feb 19, 2026
2bc045f
fix: handle plain text 500 server errors in queuePrompt
jaeone94 Feb 20, 2026
4aa27c7
fix: hide See Error button in SectionWidgets when ShowErrorsTab is di…
jaeone94 Feb 20, 2026
bbb2a44
style: truncate and wrap long error messages
jaeone94 Feb 20, 2026
c47ae8c
feat(settings): enable ShowErrorsTab by default
jaeone94 Feb 20, 2026
6896998
Merge branch 'main' into feat/node-specific-error-tab
jaeone94 Feb 20, 2026
0960ba5
[automated] Apply ESLint and Oxfmt fixes
actions-user Feb 20, 2026
0d3c9e0
refactor: address PR review feedback for error handling
jaeone94 Feb 20, 2026
54925c1
Merge branch 'main' into feat/node-specific-error-tab
jaeone94 Feb 20, 2026
ad92bc2
fix: update Playwright tests to target ErrorOverlay instead of legacy…
jaeone94 Feb 20, 2026
09ab537
Merge branch 'main' into feat/node-specific-error-tab
jaeone94 Feb 20, 2026
60b04c7
[automated] Update test expectations
invalid-email-address Feb 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/components/TopMenuSection.vue
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@
</Button>
</div>
</div>
<ErrorOverlay />
<QueueProgressOverlay
v-if="isQueueProgressOverlayEnabled"
v-model:expanded="isQueueOverlayExpanded"
Expand Down Expand Up @@ -156,6 +157,7 @@ import StatusBadge from '@/components/common/StatusBadge.vue'
import QueueInlineProgressSummary from '@/components/queue/QueueInlineProgressSummary.vue'
import QueueNotificationBannerHost from '@/components/queue/QueueNotificationBannerHost.vue'
import QueueProgressOverlay from '@/components/queue/QueueProgressOverlay.vue'
import ErrorOverlay from '@/components/error/ErrorOverlay.vue'
import ActionBarButtons from '@/components/topbar/ActionBarButtons.vue'
import CurrentUserButton from '@/components/topbar/CurrentUserButton.vue'
import LoginButton from '@/components/topbar/LoginButton.vue'
Expand Down
104 changes: 104 additions & 0 deletions src/components/error/ErrorOverlay.vue
Comment thread
christian-byrne marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
<template>
<Transition
enter-active-class="transition-all duration-300 ease-out"
enter-from-class="-translate-y-3 opacity-0"
enter-to-class="translate-y-0 opacity-100"
>
<div v-if="isVisible" class="flex justify-end w-full pointer-events-none">
<div
role="alert"
aria-live="assertive"
class="pointer-events-auto flex w-80 min-w-72 flex-col overflow-hidden rounded-lg border border-interface-stroke bg-comfy-menu-bg shadow-interface transition-colors duration-200 ease-in-out"
>
<!-- Header -->
<div class="flex h-12 items-center gap-2 px-4">
<span class="flex-1 text-sm font-bold text-destructive-background">
{{ errorCountLabel }}
</span>
<Button
variant="muted-textonly"
size="icon-sm"
:aria-label="t('g.close')"
@click="dismiss"
>
<i class="icon-[lucide--x] block size-5 leading-none" />
</Button>
</div>

<!-- Body -->
<div class="px-4 pb-3">
<ul class="m-0 flex list-none flex-col gap-1.5 p-0">
<li
v-for="(message, idx) in groupedErrorMessages"
:key="idx"
class="flex items-baseline gap-2 text-sm leading-snug text-muted-foreground min-w-0"
>
<span
class="mt-1.5 size-1 shrink-0 rounded-full bg-muted-foreground"
/>
<span class="break-words line-clamp-3 whitespace-pre-wrap">{{
message
}}</span>
</li>
</ul>
</div>

<!-- Footer -->
<div class="flex items-center justify-end gap-4 px-4 py-3">
<Button variant="muted-textonly" size="unset" @click="dismiss">
{{ t('g.dismiss') }}
</Button>
<Button variant="secondary" size="lg" @click="seeErrors">
{{ t('errorOverlay.seeErrors') }}
</Button>
</div>
</div>
</div>
</Transition>
</template>

<script setup lang="ts">
import { computed, ref } from 'vue'
import { useI18n } from 'vue-i18n'
import { storeToRefs } from 'pinia'

import Button from '@/components/ui/button/Button.vue'
import { useExecutionStore } from '@/stores/executionStore'
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useErrorGroups } from '@/components/rightSidePanel/errors/useErrorGroups'

const { t } = useI18n()
const executionStore = useExecutionStore()
const rightSidePanelStore = useRightSidePanelStore()
const canvasStore = useCanvasStore()

const { totalErrorCount, isErrorOverlayOpen } = storeToRefs(executionStore)
const { groupedErrorMessages } = useErrorGroups(ref(''), t)

const errorCountLabel = computed(() =>
t(
'errorOverlay.errorCount',
{ count: totalErrorCount.value },
totalErrorCount.value
)
)

const isVisible = computed(
() => isErrorOverlayOpen.value && totalErrorCount.value > 0
)

function dismiss() {
executionStore.dismissErrorOverlay()
}

function seeErrors() {
if (canvasStore.canvas) {
canvasStore.canvas.deselectAll()
canvasStore.updateSelectedItems()
}

rightSidePanelStore.openPanel('errors')
executionStore.dismissErrorOverlay()
}
</script>
48 changes: 24 additions & 24 deletions src/components/rightSidePanel/RightSidePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
import type { RightSidePanelTab } from '@/stores/workspace/rightSidePanelStore'
import { resolveNodeDisplayName } from '@/utils/nodeTitleUtil'
import { cn } from '@/utils/tailwindUtil'
import { isGroupNode } from '@/utils/executableGroupNodeDto'

import TabError from './TabError.vue'
import TabInfo from './info/TabInfo.vue'
import TabGlobalParameters from './parameters/TabGlobalParameters.vue'
import TabNodes from './parameters/TabNodes.vue'
Expand All @@ -41,7 +41,7 @@ const rightSidePanelStore = useRightSidePanelStore()
const settingStore = useSettingStore()
const { t } = useI18n()

const { hasAnyError } = storeToRefs(executionStore)
const { hasAnyError, allErrorExecutionIds } = storeToRefs(executionStore)

const { findParentGroup } = useGraphHierarchy()

Expand Down Expand Up @@ -96,30 +96,31 @@ type RightSidePanelTabList = Array<{
icon?: string
}>

//FIXME all errors if nothing selected?
const selectedNodeErrors = computed(() =>
selectedNodes.value
.map((node) => executionStore.getNodeErrors(`${node.id}`))
.filter((nodeError) => !!nodeError)
const hasDirectNodeError = computed(() =>
selectedNodes.value.some((node) =>
executionStore.activeGraphErrorNodeIds.has(String(node.id))
)
)

const hasContainerInternalError = computed(() => {
if (allErrorExecutionIds.value.length === 0) return false
return selectedNodes.value.some((node) => {
if (!(node instanceof SubgraphNode || isGroupNode(node))) return false
return executionStore.hasInternalErrorForNode(node.id)
})
})

const hasRelevantErrors = computed(() => {
if (!hasSelection.value) return hasAnyError.value
return hasDirectNodeError.value || hasContainerInternalError.value
})

const tabs = computed<RightSidePanelTabList>(() => {
const list: RightSidePanelTabList = []
if (
selectedNodeErrors.value.length &&
settingStore.get('Comfy.RightSidePanel.ShowErrorsTab')
) {
list.push({
label: () => t('g.error'),
value: 'error',
icon: 'icon-[lucide--octagon-alert] bg-node-stroke-error ml-1'
})
}

if (
hasAnyError.value &&
!hasSelection.value &&
settingStore.get('Comfy.RightSidePanel.ShowErrorsTab')
settingStore.get('Comfy.RightSidePanel.ShowErrorsTab') &&
hasRelevantErrors.value
) {
list.push({
label: () => t('rightSidePanel.errors'),
Expand Down Expand Up @@ -315,9 +316,9 @@ function handleProxyWidgetsUpdate(value: ProxyWidgetsProperty) {

<!-- Panel Content -->
<div class="scrollbar-thin flex-1 overflow-y-auto">
<template v-if="!hasSelection">
<TabErrors v-if="activeTab === 'errors'" />
<TabGlobalParameters v-else-if="activeTab === 'parameters'" />
<TabErrors v-if="activeTab === 'errors'" />
<template v-else-if="!hasSelection">
<TabGlobalParameters v-if="activeTab === 'parameters'" />
<TabNodes v-else-if="activeTab === 'nodes'" />
<TabGlobalSettings v-else-if="activeTab === 'settings'" />
</template>
Expand All @@ -326,7 +327,6 @@ function handleProxyWidgetsUpdate(value: ProxyWidgetsProperty) {
:node="selectedSingleNode"
/>
<template v-else>
<TabError v-if="activeTab === 'error'" :errors="selectedNodeErrors" />
<TabSubgraphInputs
v-if="activeTab === 'parameters' && isSingleSubgraphNode"
:node="selectedSingleNode as SubgraphNode"
Expand Down
30 changes: 0 additions & 30 deletions src/components/rightSidePanel/TabError.vue

This file was deleted.

40 changes: 31 additions & 9 deletions src/components/rightSidePanel/errors/ErrorNodeCard.vue
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
<template>
<div class="overflow-hidden">
<!-- Card Header (Node ID & Actions) -->
<div v-if="card.nodeId" class="flex flex-wrap items-center gap-2 py-2">
<!-- Card Header -->
<div
v-if="card.nodeId && !compact"
class="flex flex-wrap items-center gap-2 py-2"
>
<span
v-if="showNodeIdBadge"
class="shrink-0 rounded-md bg-secondary-background-selected px-2 py-0.5 text-[10px] font-mono text-muted-foreground font-bold"
class="shrink-0 rounded-md bg-secondary-background-selected px-2 py-0.5 text-xs font-mono text-muted-foreground font-bold"
>
#{{ card.nodeId }}
</span>
Expand All @@ -19,15 +22,16 @@
variant="secondary"
size="sm"
class="rounded-lg text-sm shrink-0"
@click.stop="emit('enterSubgraph', card.nodeId ?? '')"
@click.stop="handleEnterSubgraph"
>
{{ t('rightSidePanel.enterSubgraph') }}
</Button>
<Button
variant="textonly"
size="icon-sm"
class="size-7 text-muted-foreground hover:text-base-foreground shrink-0"
@click.stop="emit('locateNode', card.nodeId ?? '')"
:aria-label="t('rightSidePanel.locateNode')"
@click.stop="handleLocateNode"
>
<i class="icon-[lucide--locate] size-3.5" />
</Button>
Expand All @@ -43,8 +47,8 @@
>
<!-- Error Message -->
<p
v-if="error.message"
class="m-0 text-sm break-words whitespace-pre-wrap leading-relaxed px-0.5"
v-if="error.message && !compact"
class="m-0 text-sm break-words whitespace-pre-wrap leading-relaxed px-0.5 max-h-[4lh] overflow-y-auto"
>
{{ error.message }}
</p>
Expand All @@ -69,7 +73,7 @@
<Button
variant="secondary"
size="sm"
class="w-full justify-center gap-2 h-8 text-[11px]"
class="w-full justify-center gap-2 h-8 text-xs"
@click="handleCopyError(error)"
>
<i class="icon-[lucide--copy] size-3.5" />
Expand All @@ -88,9 +92,15 @@ import { cn } from '@/utils/tailwindUtil'

import type { ErrorCardData, ErrorItem } from './types'

const { card, showNodeIdBadge = false } = defineProps<{
const {
card,
showNodeIdBadge = false,
compact = false
} = defineProps<{
card: ErrorCardData
showNodeIdBadge?: boolean
/** Hide card header and error message (used in single-node selection mode) */
compact?: boolean
}>()

const emit = defineEmits<{
Expand All @@ -101,6 +111,18 @@ const emit = defineEmits<{

const { t } = useI18n()

function handleLocateNode() {
if (card.nodeId) {
emit('locateNode', card.nodeId)
}
}

function handleEnterSubgraph() {
if (card.nodeId) {
emit('enterSubgraph', card.nodeId)
}
}

function handleCopyError(error: ErrorItem) {
emit(
'copyToClipboard',
Expand Down
1 change: 1 addition & 0 deletions src/components/rightSidePanel/errors/TabErrors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ vi.mock('@/scripts/app', () => ({

vi.mock('@/utils/graphTraversalUtil', () => ({
getNodeByExecutionId: vi.fn(),
getRootParentNode: vi.fn(() => null),
forEachNode: vi.fn()
}))

Expand Down
Loading
Loading