diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx
index 7bb92c1494..66e4befb08 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNode.tsx
@@ -8,6 +8,7 @@ import {
useInputFieldNamesConnection,
useInputFieldNamesMissing,
} from 'features/nodes/hooks/useInputFieldNamesByStatus';
+import { useNodeHasErrors } from 'features/nodes/hooks/useNodeIsInvalid';
import { useOutputFieldNames } from 'features/nodes/hooks/useOutputFieldNames';
import { useWithFooter } from 'features/nodes/hooks/useWithFooter';
import { memo } from 'react';
@@ -37,11 +38,12 @@ const sx: SystemStyleObject = {
};
const InvocationNode = ({ nodeId, isOpen }: Props) => {
+ const isInvalid = useNodeHasErrors();
const withFooter = useWithFooter();
return (
<>
-
+
{isOpen && (
<>
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeHeader.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeHeader.tsx
index 42a0bf1ec0..016f96e287 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeHeader.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/InvocationNodeHeader.tsx
@@ -3,7 +3,6 @@ import { Flex } from '@invoke-ai/ui-library';
import NodeCollapseButton from 'features/nodes/components/flow/nodes/common/NodeCollapseButton';
import NodeTitle from 'features/nodes/components/flow/nodes/common/NodeTitle';
import InvocationNodeClassificationIcon from 'features/nodes/components/flow/nodes/Invocation/InvocationNodeClassificationIcon';
-import { useNodeHasErrors } from 'features/nodes/hooks/useNodeIsInvalid';
import { memo } from 'react';
import InvocationNodeCollapsedHandles from './InvocationNodeCollapsedHandles';
@@ -13,6 +12,7 @@ import InvocationNodeStatusIndicator from './InvocationNodeStatusIndicator';
type Props = {
nodeId: string;
isOpen: boolean;
+ isInvalid?: boolean;
};
const sx: SystemStyleObject = {
@@ -28,14 +28,12 @@ const sx: SystemStyleObject = {
},
};
-const InvocationNodeHeader = ({ nodeId, isOpen }: Props) => {
- const isInvalid = useNodeHasErrors();
-
+const InvocationNodeHeader = ({ nodeId, isOpen, isInvalid }: Props) => {
return (
-
+
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTitle.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTitle.tsx
index ebbbfd9c2e..ef3020a741 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTitle.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/Invocation/fields/InputFieldTitle.tsx
@@ -30,12 +30,12 @@ const labelSx: SystemStyleObject = {
_hover: {
fontWeight: 'semibold !important',
},
- '&[data-is-invalid="true"]': {
- color: 'error.300',
- },
'&[data-is-added-to-form="true"]': {
color: 'blue.300',
},
+ '&[data-is-invalid="true"]': {
+ color: 'error.300',
+ },
'&[data-is-disabled="true"]': {
opacity: 0.5,
},
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx
index d9f77c2176..e08b3fe8d0 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeTitle.tsx
@@ -1,3 +1,4 @@
+import type { SystemStyleObject } from '@invoke-ai/ui-library';
import { Flex, Input, Text } from '@invoke-ai/ui-library';
import { useAppDispatch } from 'app/store/storeHooks';
import { useEditable } from 'common/hooks/useEditable';
@@ -10,12 +11,20 @@ import { NO_FIT_ON_DOUBLE_CLICK_CLASS } from 'features/nodes/types/constants';
import { memo, useCallback, useMemo, useRef } from 'react';
import { useTranslation } from 'react-i18next';
+const labelSx: SystemStyleObject = {
+ fontWeight: 'semibold',
+ '&[data-is-invalid="true"]': {
+ color: 'error.300',
+ },
+};
+
type Props = {
nodeId: string;
title?: string;
+ isInvalid?: boolean;
};
-const NodeTitle = ({ nodeId, title }: Props) => {
+const NodeTitle = ({ nodeId, title, isInvalid }: Props) => {
const dispatch = useAppDispatch();
const label = useNodeUserTitleSafe();
const batchGroupId = useBatchGroupId(nodeId);
@@ -53,10 +62,11 @@ const NodeTitle = ({ nodeId, title }: Props) => {
{!editable.isEditing && (
{titleWithBatchGroupId}
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx
index 26d46b1b2f..315ca57e9e 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/NodeWrapper.tsx
@@ -5,6 +5,7 @@ import { useInvocationNodeContext } from 'features/nodes/components/flow/nodes/I
import { useIsWorkflowEditorLocked } from 'features/nodes/hooks/useIsWorkflowEditorLocked';
import { useMouseOverFormField, useMouseOverNode } from 'features/nodes/hooks/useMouseOverNode';
import { useNodeExecutionState } from 'features/nodes/hooks/useNodeExecutionState';
+import { useNodeHasErrors } from 'features/nodes/hooks/useNodeIsInvalid';
import { useZoomToNode } from 'features/nodes/hooks/useZoomToNode';
import { selectNodeOpacity } from 'features/nodes/store/workflowSettingsSlice';
import { DRAG_HANDLE_CLASSNAME, NO_FIT_ON_DOUBLE_CLICK_CLASS, NODE_WIDTH } from 'features/nodes/types/constants';
@@ -29,6 +30,8 @@ const NodeWrapper = (props: NodeWrapperProps) => {
const mouseOverFormField = useMouseOverFormField(nodeId);
const zoomToNode = useZoomToNode(nodeId);
const isLocked = useIsWorkflowEditorLocked();
+ const isInvalid = useNodeHasErrors();
+ const hasError = isMissingTemplate || isInvalid;
const executionState = useNodeExecutionState(nodeId);
const isInProgress = executionState?.status === zNodeStatus.enum.IN_PROGRESS;
@@ -74,7 +77,7 @@ const NodeWrapper = (props: NodeWrapperProps) => {
data-is-editor-locked={isLocked}
data-is-selected={selected}
data-is-mouse-over-form-field={mouseOverFormField.isMouseOverFormField}
- data-status={isMissingTemplate ? 'error' : needsUpdate ? 'warning' : undefined}
+ data-status={hasError ? 'error' : needsUpdate ? 'warning' : undefined}
>
diff --git a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/shared.ts b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/shared.ts
index 721c816b19..95b5b9c9e8 100644
--- a/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/shared.ts
+++ b/invokeai/frontend/web/src/features/nodes/components/flow/nodes/common/shared.ts
@@ -56,6 +56,9 @@ export const containerSx: SystemStyleObject = {
display: 'block',
shadow: '0 0 0 2px var(--border-color-selected)',
},
+ '&[data-is-invalid="true"]': {
+ color: 'error.300',
+ },
'&[data-is-editor-locked="true"]': {
'& *': {
cursor: 'not-allowed',