Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d0cfb15
Add node bottom button
AustinMroz Feb 2, 2026
a17436b
Add error tab to right side panel
AustinMroz Feb 3, 2026
19dae0e
Minor styling tweaks
AustinMroz Feb 3, 2026
a1147e2
Add error indicator on widget label
AustinMroz Feb 3, 2026
ff40bfd
Fix error hover bg
AustinMroz Feb 3, 2026
38bc520
Add copy to clipboard button
AustinMroz Feb 3, 2026
2236888
Rework badge display in vue mode
AustinMroz Feb 3, 2026
0dafeff
Fix node sizing, resize indicator on hover
AustinMroz Feb 3, 2026
482c5c0
Move price badges back into header
AustinMroz Feb 4, 2026
8591f25
truncate all
AustinMroz Feb 4, 2026
b3ab82e
no bg
AustinMroz Feb 4, 2026
c45ed2f
functional background and sizing
AustinMroz Feb 4, 2026
ffcfd01
fix localization
AustinMroz Feb 4, 2026
374875e
Remove core comfy badge from api nodes
AustinMroz Feb 4, 2026
fcb6c6d
Add translation to label
AustinMroz Feb 4, 2026
52b91e7
Fix tests
AustinMroz Feb 4, 2026
48ce8f8
Fix empty padding with no badges
AustinMroz Feb 4, 2026
afed8cb
Fix widget scaling and revert valid test
AustinMroz Feb 4, 2026
dde1f88
Another less destructive stab at sizing
AustinMroz Feb 4, 2026
3357656
Fix colored nodes
AustinMroz Feb 4, 2026
85dcf99
Fix node header and light theme badge color
AustinMroz Feb 5, 2026
8f6aa9d
Fix compatibility across badge display settings
AustinMroz Feb 6, 2026
2a735f6
Reorder badges, prefix id with octothorpe
AustinMroz Feb 7, 2026
039cef1
Fix parameter sorting
AustinMroz Feb 11, 2026
a2ed617
[automated] Update test expectations
invalid-email-address Feb 11, 2026
190b93d
Fix lint/tests
AustinMroz Feb 11, 2026
3495e1d
Nits
AustinMroz Feb 11, 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
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions packages/design-system/src/icons/comfy-c.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
20 changes: 20 additions & 0 deletions src/components/rightSidePanel/RightSidePanel.vue
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import { SubgraphNode } from '@/lib/litegraph/src/litegraph'
import type { LGraphNode } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useExecutionStore } from '@/stores/executionStore'
import { useRightSidePanelStore } from '@/stores/workspace/rightSidePanelStore'
import type { RightSidePanelTab } from '@/stores/workspace/rightSidePanelStore'
import { resolveNodeDisplayName } from '@/utils/nodeTitleUtil'
import { cn } from '@/utils/tailwindUtil'

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 @@ -33,6 +35,7 @@ import {
import SubgraphEditor from './subgraph/SubgraphEditor.vue'

const canvasStore = useCanvasStore()
const executionStore = useExecutionStore()
const rightSidePanelStore = useRightSidePanelStore()
const settingStore = useSettingStore()
const { t } = useI18n()
Expand Down Expand Up @@ -87,10 +90,25 @@ function closePanel() {
type RightSidePanelTabList = Array<{
label: () => string
value: RightSidePanelTab
icon?: string
}>

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

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

list.push({
label: () =>
Expand Down Expand Up @@ -271,6 +289,7 @@ function handleProxyWidgetsUpdate(value: ProxyWidgetsProperty) {
:value="tab.value"
>
{{ tab.label() }}
<i v-if="tab.icon" :class="cn(tab.icon, 'size-4')" />
</Tab>
</TabList>
</nav>
Expand All @@ -288,6 +307,7 @@ 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: 30 additions & 0 deletions src/components/rightSidePanel/TabError.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
<script setup lang="ts">
import { useI18n } from 'vue-i18n'
import Button from '@/components/ui/button/Button.vue'
import { useCopyToClipboard } from '@/composables/useCopyToClipboard'
import type { NodeError } from '@/schemas/apiSchema'
const { t } = useI18n()
defineProps<{
errors: NodeError[]
}>()
const { copyToClipboard } = useCopyToClipboard()
</script>
<template>
<div class="m-4">
<Button class="w-full" @click="copyToClipboard(JSON.stringify(errors))">
{{ t('g.copy') }}
<i class="icon-[lucide--copy] size-4" />
</Button>
</div>
<div
v-for="(error, index) in errors.flatMap((ne) => ne.errors)"
:key="index"
class="px-2"
>
<h3 class="text-error" v-text="error.message" />
<div class="text-muted-foreground" v-text="error.details" />
</div>
</template>
5 changes: 4 additions & 1 deletion src/composables/node/usePriceBadge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ export const usePriceBadge = () => {
return badges
}

function isCreditsBadge(badge: LGraphBadge | (() => LGraphBadge)): boolean {
function isCreditsBadge(
badge: Partial<LGraphBadge> | (() => Partial<LGraphBadge>)
): boolean {
const badgeInstance = typeof badge === 'function' ? badge() : badge
return badgeInstance.icon?.image === componentIconSvg
}
Expand All @@ -61,6 +63,7 @@ export const usePriceBadge = () => {
}
return {
getCreditsBadge,
isCreditsBadge,
updateSubgraphCredits
}
}
1 change: 1 addition & 0 deletions src/locales/en/main.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"icon": "Icon",
"color": "Color",
"error": "Error",
"enterSubgraph": "Enter Subgraph",
"resizeFromBottomRight": "Resize from bottom-right corner",
"resizeFromTopRight": "Resize from top-right corner",
"resizeFromBottomLeft": "Resize from bottom-left corner",
Expand Down
26 changes: 7 additions & 19 deletions src/renderer/extensions/vueNodes/components/InputSlot.vue
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
:class="
cn(
'-translate-x-1/2 w-3',
hasSlotError &&
hasError &&
'before:ring-2 before:ring-error before:ring-offset-0 before:size-4 before:absolute before:rounded-full before:pointer-events-none'
)
"
Expand All @@ -40,7 +40,7 @@
:class="
cn(
'truncate text-node-component-slot-text',
hasSlotError && 'text-error font-medium'
hasError && 'text-error font-medium'
)
"
>
Expand All @@ -65,19 +65,19 @@ import { getSlotKey } from '@/renderer/core/layout/slots/slotIdentifier'
import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips'
import { useSlotElementTracking } from '@/renderer/extensions/vueNodes/composables/useSlotElementTracking'
import { useSlotLinkInteraction } from '@/renderer/extensions/vueNodes/composables/useSlotLinkInteraction'
import { useExecutionStore } from '@/stores/executionStore'
import { cn } from '@/utils/tailwindUtil'

import SlotConnectionDot from './SlotConnectionDot.vue'

interface InputSlotProps {
nodeType?: string
nodeId?: string
slotData: INodeSlot
index: number
connected?: boolean
compatible?: boolean
connected?: boolean
dotOnly?: boolean
hasError?: boolean
index: number
nodeType?: string
nodeId?: string
socketless?: boolean
}

Expand All @@ -91,18 +91,6 @@ const hasNoLabel = computed(
)
const dotOnly = computed(() => props.dotOnly || hasNoLabel.value)

const executionStore = useExecutionStore()

const hasSlotError = computed(() => {
const nodeErrors = executionStore.lastNodeErrors?.[props.nodeId ?? '']
if (!nodeErrors) return false

const slotName = props.slotData.name
return nodeErrors.errors.some(
(error) => error.extra_info?.input_name === slotName
)
})

const renderError = ref<string | null>(null)
const { toastErrorHandler } = useErrorHandling()

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'

import type {
LGraph,
LGraphNode,
LGraphNode as LGLGraphNode,
SubgraphNode
} from '@/lib/litegraph/src/litegraph'
import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import NodeHeader from '@/renderer/extensions/vueNodes/components/NodeHeader.vue'
import LGraphNode from '@/renderer/extensions/vueNodes/components/LGraphNode.vue'
import { getNodeByLocatorId } from '@/utils/graphTraversalUtil'

const mockApp: { rootGraph?: Partial<LGraph> } = vi.hoisted(() => ({}))
Expand Down Expand Up @@ -56,15 +56,15 @@ vi.mock('@/i18n', () => ({
}
}))

describe('NodeHeader - Subgraph Functionality', () => {
describe('Vue Node - Subgraph Functionality', () => {
// Helper to setup common mocks
const setupMocks = async (isSubgraph = true, hasGraph = true) => {
if (hasGraph) mockApp.rootGraph = {}
else mockApp.rootGraph = undefined

vi.mocked(getNodeByLocatorId).mockReturnValue({
isSubgraphNode: (): this is SubgraphNode => isSubgraph
} as LGraphNode)
} as LGLGraphNode)
}

beforeEach(() => {
Expand All @@ -89,8 +89,8 @@ describe('NodeHeader - Subgraph Functionality', () => {
flags: {}
})

const createWrapper = (props = {}) => {
return mount(NodeHeader, {
const createWrapper = (props: { nodeData: VueNodeData }) => {
return mount(LGraphNode, {
props,
global: {
plugins: [createTestingPinia({ createSpy: vi.fn })],
Expand All @@ -106,8 +106,7 @@ describe('NodeHeader - Subgraph Functionality', () => {
await setupMocks(true) // isSubgraph = true

const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1'),
readonly: false
nodeData: createMockNodeData('test-node-1')
})

await wrapper.vm.$nextTick()
Expand All @@ -120,8 +119,7 @@ describe('NodeHeader - Subgraph Functionality', () => {
await setupMocks(false) // isSubgraph = false

const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1'),
readonly: false
nodeData: createMockNodeData('test-node-1')
})

await wrapper.vm.$nextTick()
Expand All @@ -130,29 +128,11 @@ describe('NodeHeader - Subgraph Functionality', () => {
expect(subgraphButton.exists()).toBe(false)
})

it('should emit enter-subgraph event when button is clicked', async () => {
await setupMocks(true) // isSubgraph = true

const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1'),
readonly: false
})

await wrapper.vm.$nextTick()

const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
await subgraphButton.trigger('click')

expect(wrapper.emitted('enter-subgraph')).toBeTruthy()
expect(wrapper.emitted('enter-subgraph')).toHaveLength(1)
})

it('should handle subgraph context correctly', async () => {
await setupMocks(true) // isSubgraph = true

const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1', 'subgraph-id'),
readonly: false
nodeData: createMockNodeData('test-node-1', 'subgraph-id')
})

await wrapper.vm.$nextTick()
Expand All @@ -167,26 +147,11 @@ describe('NodeHeader - Subgraph Functionality', () => {
expect(subgraphButton.exists()).toBe(true)
})

it('should handle missing graph gracefully', async () => {
await setupMocks(true, false) // isSubgraph = true, hasGraph = false

const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1'),
readonly: false
})

await wrapper.vm.$nextTick()

const subgraphButton = wrapper.find('[data-testid="subgraph-enter-button"]')
expect(subgraphButton.exists()).toBe(false)
})

it('should prevent event propagation on double click', async () => {
await setupMocks(true) // isSubgraph = true

const wrapper = createWrapper({
nodeData: createMockNodeData('test-node-1'),
readonly: false
nodeData: createMockNodeData('test-node-1')
})

await wrapper.vm.$nextTick()
Expand Down
Loading