From 5cca97636eec744a2d181d51bdf7418d0369c775 Mon Sep 17 00:00:00 2001 From: David Anthony Date: Thu, 25 Jun 2026 09:42:42 -0700 Subject: [PATCH 1/3] fix(apollo-react): avoid canvas story import cycle --- .../canvas/components/HierarchicalCanvas/HierarchicalCanvas.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/apollo-react/src/canvas/components/HierarchicalCanvas/HierarchicalCanvas.tsx b/packages/apollo-react/src/canvas/components/HierarchicalCanvas/HierarchicalCanvas.tsx index 0adbf4774..208b37612 100644 --- a/packages/apollo-react/src/canvas/components/HierarchicalCanvas/HierarchicalCanvas.tsx +++ b/packages/apollo-react/src/canvas/components/HierarchicalCanvas/HierarchicalCanvas.tsx @@ -58,7 +58,7 @@ import { AddNodeManager } from '../AddNodePanel/AddNodeManager'; import { AddNodePreview } from '../AddNodePanel/AddNodePreview'; import { createAddNodePreview } from '../AddNodePanel/createAddNodePreview'; import { BaseCanvas, type BaseCanvasRef } from '../BaseCanvas'; -import { BaseNode } from '../BaseNode'; +import { BaseNode } from '../BaseNode/BaseNode'; import { BlankCanvasNode } from '../BlankCanvasNode'; import { CanvasPositionControls } from '../CanvasPositionControls'; import { LoopCanvasNode } from '../LoopNode'; From 52ec7bc311d56b086e7df235ec99af293f3c7d29 Mon Sep 17 00:00:00 2001 From: David Anthony Date: Fri, 26 Jun 2026 07:09:07 -0700 Subject: [PATCH 2/3] feat(apollo-react): add property panel alerts story --- .../NodePropertyPanel.stories.tsx | 418 +++++++++++++++++- 1 file changed, 415 insertions(+), 3 deletions(-) diff --git a/packages/apollo-react/src/canvas/components/NodePropertyPanel/NodePropertyPanel.stories.tsx b/packages/apollo-react/src/canvas/components/NodePropertyPanel/NodePropertyPanel.stories.tsx index 62a99b36e..2314f2e78 100644 --- a/packages/apollo-react/src/canvas/components/NodePropertyPanel/NodePropertyPanel.stories.tsx +++ b/packages/apollo-react/src/canvas/components/NodePropertyPanel/NodePropertyPanel.stories.tsx @@ -2,6 +2,10 @@ import MonacoEditor from '@monaco-editor/react'; import type { Meta, StoryObj } from '@storybook/react-vite'; import type { FormSchema } from '@uipath/apollo-wind'; import { + Alert, + AlertDescription, + AlertTitle, + Badge, cn, DropdownMenu, DropdownMenuContent, @@ -26,6 +30,7 @@ import { } from '@uipath/apollo-wind/editor-themes'; import { ChevronDown, + CircleAlert, CircleCheck, Code2, GitFork, @@ -627,6 +632,8 @@ function CasePanel({ monacoTheme, defaultExpanded = false, defaultValue = '', + errorMessage, + errorAction, }: { caseTitle: string; onTitleChange: (title: string) => void; @@ -634,13 +641,21 @@ function CasePanel({ monacoTheme: string; defaultExpanded?: boolean; defaultValue?: string; + errorMessage?: string; + errorAction?: string; }) { const [expanded, setExpanded] = useState(defaultExpanded); const [editingTitle, setEditingTitle] = useState(false); const titleRef = useRef(null); + const hasError = Boolean(errorMessage); return ( -
+
{/* Card header */}
@@ -650,7 +665,10 @@ function CasePanel({ type="button" onClick={() => setExpanded((v) => !v)} aria-label={expanded ? 'Collapse case' : 'Expand case'} - className="grid size-5 shrink-0 place-items-center rounded text-foreground-subtle transition hover:text-foreground" + className={cn( + 'grid size-5 shrink-0 place-items-center rounded transition hover:text-foreground', + hasError ? 'text-error' : 'text-foreground-subtle' + )} > )} + {hasError && ( + + 1 + + )} + ))} +
+ + + ); +} + +function ErrorFieldBlock({ + title, + message, + action, +}: { + title: string; + message: string; + action: string; +}) { + return ( +
+
+ +
+

{title}

+

{message}

+

{action}

+
+
+
+ ); +} + +const ALERT_PATTERN_NOTES = [ + { + title: 'Panel status count', + description: 'Shows total issues on the node identity area before the user scans the tabs.', + }, + { + title: 'Tab error count', + description: 'Marks each tab with the number of issues inside that section.', + }, + { + title: 'Issue summary', + description: + 'Lists every blocking issue with its location and a direct path to the affected tab.', + }, + { + title: 'Inline field error', + description: 'Places the message and next action directly under the invalid code editor.', + }, + { + title: 'Section error block', + description: + 'Explains tab-specific configuration problems when the field is not currently visible.', + }, +]; + +function AlertPatternNoteCard() { + return ( +
+
+ +
+

Alert and error types included

+

+ The story combines global navigation cues with local, action-oriented messages. +

+
+
+
+ {ALERT_PATTERN_NOTES.map((note) => ( +
+ +

+ {note.title}: {note.description} +

+
+ ))} +
+
+ ); +} + function CompactEditorStory() { const monacoTheme = useMonacoTheme(); const [cases, setCases] = useState([{ id: 1, title: 'Case 1' }]); @@ -897,6 +1103,207 @@ export const CompactEditor: Story = { render: () => , }; +function AlertsAndErrorsStory() { + const monacoTheme = useMonacoTheme(); + const [activeTab, setActiveTab] = useState('parameters'); + const [cases, setCases] = useState([{ id: 1, title: 'Case 1' }]); + const nextIdRef = useRef(2); + const [defaultBranch, setDefaultBranch] = useState(false); + const [label, setLabel] = useState('Switch'); + const [category, setCategory] = useState('Control'); + const [editingLabel, setEditingLabel] = useState(false); + const [editingCategory, setEditingCategory] = useState(false); + const labelRef = useRef(null); + const categoryRef = useRef(null); + + const addCase = () => { + const id = nextIdRef.current++; + setCases((prev) => [...prev, { id, title: `Case ${id}` }]); + }; + const deleteCase = (id: number) => setCases((prev) => prev.filter((c) => c.id !== id)); + const updateCaseTitle = (id: number, title: string) => + setCases((prev) => prev.map((c) => (c.id === id ? { ...c, title } : c))); + const conditionIssue = ALERT_ISSUES.find((issue) => issue.id === 'condition'); + + return ( +
+
+ Design Review: This pattern is ready + for feedback on alert placement, hierarchy, and action clarity. +
+ + {}} + contentInset="0.875rem" + className="h-[640px]" + > +
+
+
+
+ + + {ALERT_ISSUES.length} + +
+
+ {editingLabel ? ( + setLabel(e.target.value)} + onBlur={() => setEditingLabel(false)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === 'Escape') setEditingLabel(false); + }} + className="w-full rounded bg-surface-overlay px-1.5 py-0.5 text-base font-semibold leading-5 tracking-[-0.3px] text-foreground outline-none ring-1 ring-brand" + autoFocus + /> + ) : ( + + )} + {editingCategory ? ( + setCategory(e.target.value)} + onBlur={() => setEditingCategory(false)} + onKeyDown={(e) => { + if (e.key === 'Enter' || e.key === 'Escape') setEditingCategory(false); + }} + className="w-full rounded bg-surface-overlay px-1.5 py-0.5 text-xs leading-4 text-foreground outline-none ring-1 ring-brand" + autoFocus + /> + ) : ( + + )} +
+
+
+ +
+
+ + setActiveTab(value as CompactTabId)} + className="flex min-h-0 flex-1 flex-col" + > +
+ + + + + + + + + + + +
+ + +
+ +
+
+ Cases +
+
+ {cases.map((c, i) => ( + updateCaseTitle(c.id, title)} + onDelete={() => deleteCase(c.id)} + monacoTheme={monacoTheme} + defaultExpanded={i === 0} + defaultValue={i === 0 ? 'input.status' : ''} + errorMessage={i === 0 ? conditionIssue?.message : undefined} + errorAction={i === 0 ? conditionIssue?.action : undefined} + /> + ))} +
+ +
+ + Default branch +
+
+ + +
+ +
+ + Failure behavior + + +
+
+
+ + +
+ +
+ + Execution limits + + +
+
+
+
+
+
+
+ +
+ ); +} + // ============================================================================ // Input Editor // Inline expression inputs — one per case, no code editor panel. @@ -1372,6 +1779,11 @@ export const InlineEditing: Story = { render: () => , }; +export const AlertsAndErrors: Story = { + name: 'Alerts and Errors', + render: () => , +}; + const SURFACE_REMAP = { '--surface-raised': 'var(--surface-overlay)' } as CSSProperties; function CompactResponsivePanelStory() { From d92a7f61366b021256173c8809addccf8e6d19c9 Mon Sep 17 00:00:00 2001 From: David Anthony Date: Fri, 26 Jun 2026 11:33:04 -0700 Subject: [PATCH 3/3] docs(apollo-react): add AddNodePanel message type story --- .../AddNodePanel/AddNodePanel.stories.tsx | 171 +++++++++++++++++- 1 file changed, 170 insertions(+), 1 deletion(-) diff --git a/packages/apollo-react/src/canvas/components/AddNodePanel/AddNodePanel.stories.tsx b/packages/apollo-react/src/canvas/components/AddNodePanel/AddNodePanel.stories.tsx index dde42e196..8bed407cf 100644 --- a/packages/apollo-react/src/canvas/components/AddNodePanel/AddNodePanel.stories.tsx +++ b/packages/apollo-react/src/canvas/components/AddNodePanel/AddNodePanel.stories.tsx @@ -1,7 +1,7 @@ import type { Meta, StoryObj } from '@storybook/react-vite'; import type { Node } from '@uipath/apollo-react/canvas/xyflow/react'; import { Panel, Position, useReactFlow } from '@uipath/apollo-react/canvas/xyflow/react'; -import { useEffect, useMemo, useState } from 'react'; +import { useEffect, useMemo, useRef, useState } from 'react'; import { NodeRegistryProvider } from '../../core'; import { useAddNodeOnConnectEnd, useCanvasEvent } from '../../hooks'; import type { CategoryManifest, NodeManifest } from '../../schema'; @@ -438,6 +438,159 @@ function ShapeTestPanel(props: AddNodePanelProps) { ); } +type DocumentExtractionMessageState = { + id: string; + label: string; + message: string; + icon: string; + actionLabel?: string; + actionHref?: string; +}; + +const DOCUMENT_EXTRACTION_MESSAGE_STATES: DocumentExtractionMessageState[] = [ + { + id: 'availability-check-failed', + label: 'Availability check failed', + icon: 'wifi-off', + message: + "We couldn't check IXP availability. Check your connection and try again. If this keeps happening, contact your administrator.", + actionLabel: 'Try again', + }, + { + id: 'service-disabled', + label: 'Service not enabled', + icon: 'circle-alert', + message: + "Document extraction needs Document Understanding, which isn't enabled for this tenant. A tenant administrator can enable it in Admin > Tenants > Services.", + actionLabel: 'Open tenant services', + actionHref: '#admin-tenants-services', + }, + { + id: 'missing-license', + label: 'Missing license', + icon: 'badge-alert', + message: + "You don't have a Communications Mining license, so you can't use document extraction. Ask a tenant administrator to assign the required license, then reopen this panel.", + actionLabel: 'Contact administrator', + }, +]; + +function DocumentExtractionMessagePanel({ state }: { state: DocumentExtractionMessageState }) { + const panelRef = useRef(null); + + useEffect(() => { + const button = panelRef.current?.querySelector( + `#toolbox-item-document-extraction-${state.id}` + ); + button?.click(); + }, [state.id]); + + const items: ListItem[] = useMemo( + () => [ + { + id: `document-extraction-${state.id}`, + name: 'Document extraction', + icon: { name: 'file-search' }, + data: { type: 'document-extraction', category: 'Documents' }, + children: [], + }, + ], + [state.id] + ); + + return ( +
+ console.log('Selected node:', node)} + onClose={() => console.log('Closed selector')} + renderEmptyState={() => } + /> +
+ ); +} + +function DocumentExtractionMessage({ state }: { state: DocumentExtractionMessageState }) { + return ( +
+
+ +
+

{state.message}

+ {state.actionLabel && state.actionHref && ( + { + event.preventDefault(); + console.log(`${state.label}: ${state.actionLabel}`); + }} + > + {state.actionLabel} + + )} + {state.actionLabel && !state.actionHref && ( + + )} +
+ ); +} + +function DocumentExtractionMessagesStory() { + return ( +
+
+ {DOCUMENT_EXTRACTION_MESSAGE_STATES.map((state) => ( +
+ {state.label} +
+ +
+
+ ))} +
+
+ ); +} + /** * Story for testing all available node shapes (container, rectangle, square, circle). * @@ -543,6 +696,22 @@ export const AllShapes: Story = { render: () => , }; +export const NodePanelMessaging: Story = { + name: 'Add node panel message types', + parameters: { + docs: { + description: { + story: [ + 'Previews Document extraction blocker and recovery messages inside the AddNodePanel shell.', + '', + 'These states use the same title, search bar, panel width, and empty body region so the copy and visual treatment can be reviewed together.', + ].join('\n'), + }, + }, + }, + render: () => , +}; + export const NodePanelStaticItems: Story = { name: 'Add node panel with static items', args: {