diff --git a/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.stories.tsx b/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.stories.tsx
index 337fe366e..a602cb594 100644
--- a/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.stories.tsx
+++ b/packages/apollo-react/src/canvas/components/BaseNode/BaseNode.stories.tsx
@@ -6,7 +6,7 @@
import type { Meta, StoryObj } from '@storybook/react';
import { Column } from '@uipath/apollo-react/canvas/layouts';
-import type { Edge, Node } from '@uipath/apollo-react/canvas/xyflow/react';
+import type { Edge, Node, NodeProps } from '@uipath/apollo-react/canvas/xyflow/react';
import { Panel } from '@uipath/apollo-react/canvas/xyflow/react';
import { Button, cn, Input, Label, Slider, Switch } from '@uipath/apollo-wind';
import { useCallback, useEffect, useMemo, useState } from 'react';
@@ -34,9 +34,14 @@ import type { ValidationErrorSeverity } from '../../types/validation';
import { CanvasIcon } from '../../utils/icon-registry';
import { BaseCanvas } from '../BaseCanvas';
import { CanvasPositionControls } from '../CanvasPositionControls';
+import { CanvasTooltip } from '../CanvasTooltip';
import { NodeInspector } from '../NodeInspector';
-import type { BaseNodeData } from './BaseNode.types';
-import { BaseNodeOverrideConfigProvider } from './BaseNodeConfigContext';
+import { BaseNode } from './BaseNode';
+import type { BaseNodeData, NodeAdornments } from './BaseNode.types';
+import {
+ type BaseNodeOverrideConfig,
+ BaseNodeOverrideConfigProvider,
+} from './BaseNodeConfigContext';
// ============================================================================
// Meta Configuration
@@ -1496,11 +1501,74 @@ const ADORNMENT_ROWS = [
{ key: 'status-inprogress', label: 'Status: InProgress (top-right)' },
{ key: 'status-failed', label: 'Status: Failed (top-right)' },
{ key: 'start-point', label: 'Start Point (bottom-left)' },
- { key: 'square-dashed', label: 'Square Dashed (bottom-right)' },
+ { key: 'mock-static', label: 'Custom: static mock (bottom-right)' },
+ { key: 'mock-generated', label: 'Custom: generated mock (bottom-right)' },
{ key: 'all', label: 'All Adornments' },
{ key: 'multi-exec', label: 'Multi-execution (count: 5)' },
] as const;
+/**
+ * Both mocked-output icons come from the icon registry and share one truncated
+ * file outline, so static and generated read as siblings. See
+ * `FileSparklesCornerIcon` for how the sparkle is composed.
+ */
+const MOCK_OUTPUT_VARIANTS = {
+ static: {
+ generated: false,
+ icon: 'file-braces-corner',
+ tooltip: 'Node output is mocked',
+ },
+ generated: {
+ generated: true,
+ icon: 'file-sparkles-corner',
+ tooltip: 'Node output is generated by LLM',
+ },
+} as const;
+
+function MockOutputIndicator({ variant }: { variant: keyof typeof MOCK_OUTPUT_VARIANTS }) {
+ const { icon, tooltip } = MOCK_OUTPUT_VARIANTS[variant];
+
+ return (
+
+
+
+
+
+ );
+}
+
+/**
+ * Bottom-right adornments per row, supplied through the node override config.
+ * The execution-status API no longer carries a mocked-output flag, so consumers
+ * own this slot and decide what it renders.
+ */
+const ADORNMENT_OVERRIDES: Record = {
+ 'mock-static': { bottomRight: },
+ 'mock-generated': { bottomRight: },
+};
+
+/** Adornment node ids are `adorn--`, and row keys may contain dashes. */
+function getAdornmentRowKey(nodeId: string): string {
+ return nodeId.split('-').slice(1, -1).join('-');
+}
+
+/**
+ * Story-only node component that wires each row's custom adornments in via
+ * `BaseNodeOverrideConfigProvider`, the same path product code uses.
+ */
+function AdornmentOverrideNode(props: NodeProps>) {
+ const overrideConfig = useMemo(
+ () => ({ adornments: ADORNMENT_OVERRIDES[getAdornmentRowKey(props.id)] }),
+ [props.id]
+ );
+
+ return (
+
+
+
+ );
+}
+
/**
* Creates nodes demonstrating all adornment types across shapes.
*/
@@ -1549,14 +1617,14 @@ function getAdornmentExecutionState(key: string) {
return { status: 'Failed' as const };
case 'start-point':
return { status: 'None' as const, isExecutionStartPoint: true };
- case 'square-dashed':
- return { status: 'None' as const, isOutputPinned: true };
+ case 'mock-static':
+ case 'mock-generated':
+ return { status: 'None' as const };
case 'all':
return {
status: 'Completed' as const,
debug: true,
isExecutionStartPoint: true,
- isOutputPinned: true,
};
case 'multi-exec':
return { status: 'Completed' as const, count: 5 };
@@ -1567,7 +1635,7 @@ function getAdornmentExecutionState(key: string) {
function AdornmentsStory() {
const initialNodes = useMemo(() => createAdornmentGrid(), []);
- const { canvasProps } = useCanvasStory({ initialNodes });
+ const { canvasProps } = useCanvasStory({ initialNodes, nodeComponent: AdornmentOverrideNode });
return (
@@ -1576,7 +1644,7 @@ function AdornmentsStory() {
);
@@ -1587,13 +1655,8 @@ export const Adornments: Story = {
decorators: [
withCanvasProviders({
executionState: {
- getNodeExecutionState: (nodeId: string) => {
- // Extract the adornment key from node IDs like "adorn-breakpoint-circle"
- const parts = nodeId.split('-');
- // Rejoin all parts between first and last to get the key (handles keys with dashes)
- const key = parts.slice(1, -1).join('-');
- return getAdornmentExecutionState(key);
- },
+ getNodeExecutionState: (nodeId: string) =>
+ getAdornmentExecutionState(getAdornmentRowKey(nodeId)),
getEdgeExecutionState: () => undefined,
},
validationState: {
diff --git a/packages/apollo-react/src/canvas/components/index.ts b/packages/apollo-react/src/canvas/components/index.ts
index 211e1498e..27e8880aa 100644
--- a/packages/apollo-react/src/canvas/components/index.ts
+++ b/packages/apollo-react/src/canvas/components/index.ts
@@ -9,6 +9,7 @@ export * from './CanvasLeftSidebar';
export * from './CanvasModeToolbar';
export * from './CanvasPositionControls';
export * from './CanvasTakeoverModal';
+export * from './CanvasTooltip';
export * from './CanvasZoomControls';
export * from './CaseFlow';
export * from './CodedAgent';
diff --git a/packages/apollo-react/src/canvas/icons/FileSparklesCornerIcon.tsx b/packages/apollo-react/src/canvas/icons/FileSparklesCornerIcon.tsx
new file mode 100644
index 000000000..1f060dd06
--- /dev/null
+++ b/packages/apollo-react/src/canvas/icons/FileSparklesCornerIcon.tsx
@@ -0,0 +1,51 @@
+/**
+ * File with a sparkle in an opened corner, for nodes whose output is mocked by
+ * generation. Sibling of Lucide's `file-braces-corner`, which marks a static
+ * mock: both share the same truncated file outline so the two states read as one
+ * family, and the outline stops short of the glyph so no stroke passes behind it.
+ *
+ * The glyph follows Lucide's `sparkles`: a star with a cross up-right and a dot
+ * down-left, seated on a 45 degree axis at offsets of +/-8 scaled by the star's
+ * 0.5, so +/-4 from the star's centre at (8, 17). Every mark stays inside the
+ * 2..22 box Lucide keeps its paths in, and the cluster clears the outline's cut
+ * end at x=14.
+ *
+ * Every mark runs lighter than the outline's 2: the star's interior counter
+ * closes up and fills in solid at that weight, so a concave glyph needs a
+ * thinner stroke than a plain outline does to read at the same size.
+ */
+export const FileSparklesCornerIcon = ({
+ w = 24,
+ h = 24,
+ color = 'currentColor',
+}: {
+ w?: number | string;
+ h?: number | string;
+ color?: string;
+}) => (
+
+);
diff --git a/packages/apollo-react/src/canvas/icons/index.ts b/packages/apollo-react/src/canvas/icons/index.ts
index 3714926f3..b46ad0d44 100644
--- a/packages/apollo-react/src/canvas/icons/index.ts
+++ b/packages/apollo-react/src/canvas/icons/index.ts
@@ -25,6 +25,7 @@ export { DecisionIcon } from './DecisionIcon';
export { EarlyExitStatusIcon } from './EarlyExitStatusIcon';
export { EntryConditionIcon } from './EntryConditionIcon';
export { ExitConditionIcon } from './ExitConditionIcon';
+export { FileSparklesCornerIcon } from './FileSparklesCornerIcon';
export { FlaskRunIcon } from './FlaskRunIcon';
export { FlowProject } from './FlowProject';
export { FunctionProject } from './FunctionProject';
diff --git a/packages/apollo-react/src/canvas/types/execution.ts b/packages/apollo-react/src/canvas/types/execution.ts
index 2ab49dc0b..5d2c607cb 100644
--- a/packages/apollo-react/src/canvas/types/execution.ts
+++ b/packages/apollo-react/src/canvas/types/execution.ts
@@ -22,6 +22,12 @@ export interface NodeExecutionStateWithDebug {
count?: number;
debug?: boolean;
isExecutionStartPoint?: boolean;
+ /**
+ * @deprecated No longer read. The bottom-right adornment slot is owned by the
+ * consumer: supply `adornments.bottomRight` through `BaseNodeOverrideConfig`
+ * instead. Setting this has no effect and the field will be removed in the
+ * next major.
+ */
isOutputPinned?: boolean;
}
diff --git a/packages/apollo-react/src/canvas/utils/adornment-resolver.test.tsx b/packages/apollo-react/src/canvas/utils/adornment-resolver.test.tsx
index aac25b752..624d897a8 100644
--- a/packages/apollo-react/src/canvas/utils/adornment-resolver.test.tsx
+++ b/packages/apollo-react/src/canvas/utils/adornment-resolver.test.tsx
@@ -78,16 +78,6 @@ describe('resolveAdornments', () => {
expect(result.bottomLeft).toBeTruthy();
});
- // ── Square dashed (bottomRight) ─────────────────────────
-
- it('shows square dashed indicator when isOutputPinned', () => {
- const result = resolveAdornments({
- ...baseContext,
- executionState: { status: 'None', isOutputPinned: true },
- });
- expect(result.bottomRight).toBeTruthy();
- });
-
// ── Validation errors (topRight) ────────────────────────
it('shows validation indicator for ERROR severity', () => {
diff --git a/packages/apollo-react/src/canvas/utils/adornment-resolver.tsx b/packages/apollo-react/src/canvas/utils/adornment-resolver.tsx
index 45ab4b908..516d439af 100644
--- a/packages/apollo-react/src/canvas/utils/adornment-resolver.tsx
+++ b/packages/apollo-react/src/canvas/utils/adornment-resolver.tsx
@@ -57,16 +57,6 @@ function ExecutionStatusIndicatorInternal({ status, count }: { status?: string;
);
}
-function SquareDashedIndicator() {
- return (
-
-
-
-
-
- );
-}
-
export const ExecutionStatusIndicator = memo(ExecutionStatusIndicatorInternal);
export function ValidationErrorIndicator({ message }: { message?: string }) {
@@ -104,7 +94,6 @@ const getDefaultAdornments = (
const hasBreakpoint = typeof executionState === 'object' && executionState?.debug;
const isExecutionStartPoint =
typeof executionState === 'object' && executionState?.isExecutionStartPoint;
- const isOutputPinned = typeof executionState === 'object' && executionState?.isOutputPinned;
const hasValidationError =
context.validationState?.validationStatus === ValidationErrorSeverity.ERROR ||
@@ -132,7 +121,7 @@ const getDefaultAdornments = (
topLeft: hasBreakpoint ? : undefined,
topRight: getTopRight(),
bottomLeft: isExecutionStartPoint ? : undefined,
- bottomRight: isOutputPinned ? : undefined,
+ bottomRight: undefined,
};
};
diff --git a/packages/apollo-react/src/canvas/utils/icon-registry.test.tsx b/packages/apollo-react/src/canvas/utils/icon-registry.test.tsx
index 1263f99ba..2211c806b 100644
--- a/packages/apollo-react/src/canvas/utils/icon-registry.test.tsx
+++ b/packages/apollo-react/src/canvas/utils/icon-registry.test.tsx
@@ -43,6 +43,23 @@ describe('getIcon', () => {
expect(container.querySelector('#case-management-project')).toBeInTheDocument();
});
+ it('returns the FileSparklesCornerIcon for the registered file-sparkles-corner id', () => {
+ const Icon = getIcon('file-sparkles-corner');
+ const { container } = render();
+ // There is no Lucide `FileSparklesCorner`, so a registry miss would degrade to Box.
+ const svg = container.querySelector('svg.file-sparkles-corner-icon');
+ expect(svg).toBeInTheDocument();
+ // Truncated outline plus the fold, the scaled star, and the cross's two arms.
+ expect(svg?.querySelectorAll('path')).toHaveLength(5);
+ expect(svg?.querySelector('circle')).toBeInTheDocument();
+ });
+
+ it('applies the color prop to the file-sparkles-corner stroke', () => {
+ const Icon = getIcon('file-sparkles-corner');
+ const { container } = render();
+ expect(container.querySelector('svg')).toHaveAttribute('stroke', 'rgb(1, 2, 3)');
+ });
+
it('returns the LayersArrowUpRight icon for the registered layers-arrow-up-right id', () => {
const Icon = getIcon('layers-arrow-up-right');
const { container } = render();
diff --git a/packages/apollo-react/src/canvas/utils/icon-registry.tsx b/packages/apollo-react/src/canvas/utils/icon-registry.tsx
index 3e5f25ac9..dafc008db 100644
--- a/packages/apollo-react/src/canvas/utils/icon-registry.tsx
+++ b/packages/apollo-react/src/canvas/utils/icon-registry.tsx
@@ -45,6 +45,9 @@ const iconRegistry: Record = {
mcp: ({ w, h }) => ,
a2a: ({ w, h }) => ,
context: ({ w, h }) => ,
+ 'file-sparkles-corner': ({ w, h, color }) => (
+
+ ),
};
/**