From 315009e11ddec8547a16ec4e20f63c1606dfe9b4 Mon Sep 17 00:00:00 2001 From: Wojciech Krzesaj Date: Tue, 6 Oct 2026 17:36:34 +0200 Subject: [PATCH 1/2] feat(ai-studio): node types show their prototype accent colors --- apps/ai-studio/src/data/node-types.test.ts | 22 +++++++++++++++++++ apps/ai-studio/src/nodes/ai-agent/index.ts | 2 ++ apps/ai-studio/src/nodes/decision/index.ts | 1 + .../src/nodes/human-decision/index.ts | 1 + apps/ai-studio/src/nodes/trigger/index.ts | 1 + apps/ai-studio/src/nodes/visualize/index.ts | 1 + 6 files changed, 28 insertions(+) create mode 100644 apps/ai-studio/src/data/node-types.test.ts diff --git a/apps/ai-studio/src/data/node-types.test.ts b/apps/ai-studio/src/data/node-types.test.ts new file mode 100644 index 000000000..edb351bca --- /dev/null +++ b/apps/ai-studio/src/data/node-types.test.ts @@ -0,0 +1,22 @@ +import type { PaletteItem, PaletteItemOrGroup } from '@workflowbuilder/sdk'; +import { describe, expect, it } from 'vitest'; + +import { aiStudioNodeTypes } from './node-types'; + +function paletteItems(entries: PaletteItemOrGroup[]): PaletteItem[] { + return entries.flatMap((entry) => ('groupItems' in entry ? paletteItems(entry.groupItems) : [entry])); +} + +describe('aiStudioNodeTypes', () => { + it('gives every node type its prototype accent', () => { + const accents = Object.fromEntries(paletteItems(aiStudioNodeTypes).map((item) => [item.type, item.accent])); + + expect(accents).toEqual({ + 'ai-studio/trigger': 'orange', + 'ai-studio/ai-agent': 'violet-gradient', + 'ai-studio/decision': 'green', + 'ai-studio/human-decision': 'violet', + 'ai-studio/visualize': 'green', + }); + }); +}); diff --git a/apps/ai-studio/src/nodes/ai-agent/index.ts b/apps/ai-studio/src/nodes/ai-agent/index.ts index db7ed88f0..08608d4b0 100644 --- a/apps/ai-studio/src/nodes/ai-agent/index.ts +++ b/apps/ai-studio/src/nodes/ai-agent/index.ts @@ -9,6 +9,8 @@ export const aiAgentPaletteItem: PaletteItem = { description: 'Run an LLM prompt', type: 'ai-studio/ai-agent', icon: 'AiAgent', + // Renders the generic node template, which has no gradient default. + accent: 'violet-gradient', templateType: NodeType.Node, defaultPropertiesData, schema, diff --git a/apps/ai-studio/src/nodes/decision/index.ts b/apps/ai-studio/src/nodes/decision/index.ts index 58342b45d..217555e72 100644 --- a/apps/ai-studio/src/nodes/decision/index.ts +++ b/apps/ai-studio/src/nodes/decision/index.ts @@ -9,6 +9,7 @@ export const decisionPaletteItem: PaletteItem = { description: 'Route by condition', type: 'ai-studio/decision', icon: 'ArrowsSplit', + accent: 'green', templateType: NodeType.DecisionNode, defaultPropertiesData, schema, diff --git a/apps/ai-studio/src/nodes/human-decision/index.ts b/apps/ai-studio/src/nodes/human-decision/index.ts index 4efa0601c..b7b212806 100644 --- a/apps/ai-studio/src/nodes/human-decision/index.ts +++ b/apps/ai-studio/src/nodes/human-decision/index.ts @@ -19,6 +19,7 @@ export const humanDecisionPaletteItem: PaletteItem = { description: 'A person decides before the run continues', type: humanDecisionNodeType, icon: 'UserCheck', + accent: 'violet', defaultPropertiesData, schema, uischema, diff --git a/apps/ai-studio/src/nodes/trigger/index.ts b/apps/ai-studio/src/nodes/trigger/index.ts index de7f3d5b2..d4f359695 100644 --- a/apps/ai-studio/src/nodes/trigger/index.ts +++ b/apps/ai-studio/src/nodes/trigger/index.ts @@ -9,6 +9,7 @@ export const triggerPaletteItem: PaletteItem = { description: 'Start the workflow', type: 'ai-studio/trigger', icon: 'Lightning', + accent: 'orange', templateType: NodeType.StartNode, isStartNode: true, defaultPropertiesData, diff --git a/apps/ai-studio/src/nodes/visualize/index.ts b/apps/ai-studio/src/nodes/visualize/index.ts index 98ae3a2ee..6329acaed 100644 --- a/apps/ai-studio/src/nodes/visualize/index.ts +++ b/apps/ai-studio/src/nodes/visualize/index.ts @@ -9,6 +9,7 @@ export const visualizePaletteItem: PaletteItem = { description: 'Render the result visually', type: 'ai-studio/visualize', icon: 'Eye', + accent: 'green', templateType: NodeType.Node, defaultPropertiesData, schema, From 934ddc27bbfa2f6f4c57c0a8a6d7400ef45940ed Mon Sep 17 00:00:00 2001 From: Wojciech Krzesaj Date: Tue, 6 Oct 2026 19:49:58 +0200 Subject: [PATCH 2/2] test(ai-studio): node accent spec catches a type without an accent --- .../human-decision-template.test.tsx | 15 +++++++++++++++ apps/ai-studio/src/data/node-types.test.ts | 18 ++++++++++-------- 2 files changed, 25 insertions(+), 8 deletions(-) diff --git a/apps/ai-studio/src/components/human-decision/node-template/human-decision-template.test.tsx b/apps/ai-studio/src/components/human-decision/node-template/human-decision-template.test.tsx index 1d9431c52..e66f483df 100644 --- a/apps/ai-studio/src/components/human-decision/node-template/human-decision-template.test.tsx +++ b/apps/ai-studio/src/components/human-decision/node-template/human-decision-template.test.tsx @@ -155,6 +155,21 @@ describe('HumanDecisionNodeTemplate', () => { expect(handles(container, 'target').map((handle) => handle.dataset['handleid'])).toEqual(['target']); }); + it("tints its icon with the node type's accent", () => { + render( + , + ); + + expect(container.querySelector('[style*="--wb-public-node-icon-color-violet"]')).not.toBeNull(); + }); + it('renders the actions inside the OptionalNodeContent slot, where the execution markers mount', () => { render( , diff --git a/apps/ai-studio/src/data/node-types.test.ts b/apps/ai-studio/src/data/node-types.test.ts index edb351bca..7bd1d3bba 100644 --- a/apps/ai-studio/src/data/node-types.test.ts +++ b/apps/ai-studio/src/data/node-types.test.ts @@ -9,14 +9,16 @@ function paletteItems(entries: PaletteItemOrGroup[]): PaletteItem[] { describe('aiStudioNodeTypes', () => { it('gives every node type its prototype accent', () => { - const accents = Object.fromEntries(paletteItems(aiStudioNodeTypes).map((item) => [item.type, item.accent])); + const accents = paletteItems(aiStudioNodeTypes) + .map((item) => [item.type, item.accent]) + .sort(([a], [b]) => String(a).localeCompare(String(b))); - expect(accents).toEqual({ - 'ai-studio/trigger': 'orange', - 'ai-studio/ai-agent': 'violet-gradient', - 'ai-studio/decision': 'green', - 'ai-studio/human-decision': 'violet', - 'ai-studio/visualize': 'green', - }); + expect(accents).toStrictEqual([ + ['ai-studio/ai-agent', 'violet-gradient'], + ['ai-studio/decision', 'green'], + ['ai-studio/human-decision', 'violet'], + ['ai-studio/trigger', 'orange'], + ['ai-studio/visualize', 'green'], + ]); }); });