From 923691fb65dac6ec38591db07295cc89bde4b03d Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 24 Mar 2026 13:17:52 +0100 Subject: [PATCH 1/8] StatusValue: Add 'status-value:new' and 'status-value:modified' variants with changeDetection feature flag --- .../vitest/src/use-test-provider-state.ts | 2 + .../components/sidebar/IconSymbols.tsx | 33 +++++++++++++- .../components/sidebar/StatusButton.tsx | 2 + .../components/sidebar/StatusContext.tsx | 4 ++ code/core/src/manager/utils/status.test.ts | 44 +++++++++++++++++++ code/core/src/manager/utils/status.tsx | 14 ++++++ code/core/src/shared/status-store/index.ts | 4 +- code/core/src/types/modules/core-common.ts | 7 +++ 8 files changed, 108 insertions(+), 2 deletions(-) diff --git a/code/addons/vitest/src/use-test-provider-state.ts b/code/addons/vitest/src/use-test-provider-state.ts index 57ef86103464..274d833aedb5 100644 --- a/code/addons/vitest/src/use-test-provider-state.ts +++ b/code/addons/vitest/src/use-test-provider-state.ts @@ -30,6 +30,8 @@ const statusValueToStoryIds = ( const statusValueToStoryIdsMap: StatusValueToStoryIds = { 'status-value:pending': [], 'status-value:success': [], + 'status-value:new': [], + 'status-value:modified': [], 'status-value:error': [], 'status-value:warning': [], 'status-value:unknown': [], diff --git a/code/core/src/manager/components/sidebar/IconSymbols.tsx b/code/core/src/manager/components/sidebar/IconSymbols.tsx index c916f74bde9d..7c91205102c1 100644 --- a/code/core/src/manager/components/sidebar/IconSymbols.tsx +++ b/code/core/src/manager/components/sidebar/IconSymbols.tsx @@ -24,6 +24,8 @@ const SUCCESS_ID = 'icon--success'; const ERROR_ID = 'icon--error'; const WARNING_ID = 'icon--warning'; const DOT_ID = 'icon--dot'; +const NEW_ID = 'icon--new'; +const MODIFIED_ID = 'icon--modified'; export const IconSymbols: FC = () => { return ( @@ -104,6 +106,24 @@ export const IconSymbols: FC = () => { + + {/* https://github.com/storybookjs/icons/blob/main/src/icons/AddIcon.tsx */} + + + + {/* https://github.com/storybookjs/icons/blob/main/src/icons/EditIcon.tsx */} + + ); }; @@ -118,7 +138,9 @@ export const UseSymbol: FC<{ | 'success' | 'error' | 'warning' - | 'dot'; + | 'dot' + | 'new' + | 'modified'; }> = ({ type }) => { if (type === 'group') { return ; @@ -155,5 +177,14 @@ export const UseSymbol: FC<{ if (type === 'dot') { return ; } + + if (type === 'new') { + return ; + } + + if (type === 'modified') { + return ; + } + return null; }; diff --git a/code/core/src/manager/components/sidebar/StatusButton.tsx b/code/core/src/manager/components/sidebar/StatusButton.tsx index 987a783cd25b..86216a3bf49b 100644 --- a/code/core/src/manager/components/sidebar/StatusButton.tsx +++ b/code/core/src/manager/components/sidebar/StatusButton.tsx @@ -18,6 +18,8 @@ const withStatusColor = ({ theme, status }: { theme: Theme; status: StatusValue color: { 'status-value:pending': defaultColor, 'status-value:success': theme.color.positive, + 'status-value:new': theme.color.positive, + 'status-value:modified': theme.color.secondary, 'status-value:error': theme.color.negative, 'status-value:warning': theme.color.warning, 'status-value:unknown': defaultColor, diff --git a/code/core/src/manager/components/sidebar/StatusContext.tsx b/code/core/src/manager/components/sidebar/StatusContext.tsx index bb703c7d8e98..a916bfe808a3 100644 --- a/code/core/src/manager/components/sidebar/StatusContext.tsx +++ b/code/core/src/manager/components/sidebar/StatusContext.tsx @@ -27,6 +27,8 @@ export const useStatusSummary = (item: Item) => { counts: { 'status-value:pending': 0, 'status-value:success': 0, + 'status-value:new': 0, + 'status-value:modified': 0, 'status-value:error': 0, 'status-value:warning': 0, 'status-value:unknown': 0, @@ -34,6 +36,8 @@ export const useStatusSummary = (item: Item) => { statusesByValue: { 'status-value:pending': {}, 'status-value:success': {}, + 'status-value:new': {}, + 'status-value:modified': {}, 'status-value:error': {}, 'status-value:warning': {}, 'status-value:unknown': {}, diff --git a/code/core/src/manager/utils/status.test.ts b/code/core/src/manager/utils/status.test.ts index 0f6fc114eb13..462650c8dbba 100644 --- a/code/core/src/manager/utils/status.test.ts +++ b/code/core/src/manager/utils/status.test.ts @@ -29,6 +29,20 @@ describe('getHighestStatus', () => { 'status-value:warning' ); }); + it('should rank new and modified between success and warning', () => { + expect( + getMostCriticalStatusValue([ + 'status-value:new', + 'status-value:modified', + 'status-value:success', + ]) + ).toBe('status-value:modified'); + }); + it('should rank warning above new', () => { + expect(getMostCriticalStatusValue(['status-value:new', 'status-value:warning'])).toBe( + 'status-value:warning' + ); + }); }); describe('getGroupStatus', () => { @@ -102,4 +116,34 @@ describe('getGroupStatus', () => { } `); }); + it('should propagate status-value:new through group aggregation', () => { + expect( + getGroupStatus(mockDataset.withRoot, { + 'group-1--child-b1': { + a: { + storyId: 'group-1--child-b1', + typeId: 'a', + value: 'status-value:new', + description: '', + title: '', + }, + }, + }) + ).toMatchInlineSnapshot(` + { + "group-1": "status-value:new", + "group-1--child-b1": "status-value:unknown", + "group-1--child-b2": "status-value:unknown", + "root-1-child-a1": "status-value:unknown", + "root-1-child-a2": "status-value:unknown", + "root-1-child-a2--grandchild-a1-1": "status-value:unknown", + "root-1-child-a2--grandchild-a1-1:test1": "status-value:unknown", + "root-1-child-a2--grandchild-a1-2": "status-value:unknown", + "root-3--child-a1": "status-value:unknown", + "root-3-child-a2": "status-value:unknown", + "root-3-child-a2--grandchild-a1-1": "status-value:unknown", + "root-3-child-a2--grandchild-a1-2": "status-value:unknown", + } + `); + }); }); diff --git a/code/core/src/manager/utils/status.tsx b/code/core/src/manager/utils/status.tsx index 7a0d1179de53..72f07d7c97bc 100644 --- a/code/core/src/manager/utils/status.tsx +++ b/code/core/src/manager/utils/status.tsx @@ -29,6 +29,8 @@ export const statusPriority: StatusValue[] = [ 'status-value:unknown', 'status-value:pending', 'status-value:success', + 'status-value:new', + 'status-value:modified', 'status-value:warning', 'status-value:error', ]; @@ -46,6 +48,18 @@ export const getStatus = memoizerific(5)((theme: Theme, status: StatusValue) => , 'currentColor', ], + ['status-value:new']: [ + + + , + theme.color.positive, + ], + ['status-value:modified']: [ + + + , + theme.color.secondary, + ], ['status-value:warning']: [ diff --git a/code/core/src/shared/status-store/index.ts b/code/core/src/shared/status-store/index.ts index c49d51075526..5631c38a45a7 100644 --- a/code/core/src/shared/status-store/index.ts +++ b/code/core/src/shared/status-store/index.ts @@ -9,8 +9,10 @@ import type { useUniversalStore as managerUseUniversalStore } from '../universal export type StatusValue = | 'status-value:pending' | 'status-value:success' - | 'status-value:error' + | 'status-value:new' + | 'status-value:modified' | 'status-value:warning' + | 'status-value:error' | 'status-value:unknown'; export type StatusTypeId = string; diff --git a/code/core/src/types/modules/core-common.ts b/code/core/src/types/modules/core-common.ts index 5f6339dd09e8..ce41760098f0 100644 --- a/code/core/src/types/modules/core-common.ts +++ b/code/core/src/types/modules/core-common.ts @@ -535,6 +535,13 @@ export interface StorybookConfigRaw { * @experimental This feature is in early development and may change significantly in future releases. */ experimentalCodeExamples?: boolean; + + /** + * Enable change detection for agentic UI review. + * + * @default true + */ + changeDetection?: boolean; }; build?: TestBuildConfig; From 422c146b0600371f19fc80b77310737c3b4c7a99 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 24 Mar 2026 19:55:08 +0100 Subject: [PATCH 2/8] StatusValue: Add 'status-value:affected' variant and update related components --- .../vitest/src/use-test-provider-state.ts | 1 + .../components/sidebar/IconSymbols.tsx | 29 ++++++++++++++++++- .../components/sidebar/StatusButton.tsx | 5 ++-- .../components/sidebar/StatusContext.tsx | 2 ++ code/core/src/manager/utils/status.tsx | 9 +++++- code/core/src/shared/status-store/index.ts | 1 + 6 files changed, 43 insertions(+), 4 deletions(-) diff --git a/code/addons/vitest/src/use-test-provider-state.ts b/code/addons/vitest/src/use-test-provider-state.ts index 274d833aedb5..2d96246632e2 100644 --- a/code/addons/vitest/src/use-test-provider-state.ts +++ b/code/addons/vitest/src/use-test-provider-state.ts @@ -32,6 +32,7 @@ const statusValueToStoryIds = ( 'status-value:success': [], 'status-value:new': [], 'status-value:modified': [], + 'status-value:affected': [], 'status-value:error': [], 'status-value:warning': [], 'status-value:unknown': [], diff --git a/code/core/src/manager/components/sidebar/IconSymbols.tsx b/code/core/src/manager/components/sidebar/IconSymbols.tsx index 7c91205102c1..e2d4b6fe8e75 100644 --- a/code/core/src/manager/components/sidebar/IconSymbols.tsx +++ b/code/core/src/manager/components/sidebar/IconSymbols.tsx @@ -26,6 +26,7 @@ const WARNING_ID = 'icon--warning'; const DOT_ID = 'icon--dot'; const NEW_ID = 'icon--new'; const MODIFIED_ID = 'icon--modified'; +const AFFECTED_ID = 'icon--affected'; export const IconSymbols: FC = () => { return ( @@ -124,6 +125,27 @@ export const IconSymbols: FC = () => { fill="currentColor" /> + + {/* Ripple/wave icon for affected stories */} + + + + ); }; @@ -140,7 +162,8 @@ export const UseSymbol: FC<{ | 'warning' | 'dot' | 'new' - | 'modified'; + | 'modified' + | 'affected'; }> = ({ type }) => { if (type === 'group') { return ; @@ -186,5 +209,9 @@ export const UseSymbol: FC<{ return ; } + if (type === 'affected') { + return ; + } + return null; }; diff --git a/code/core/src/manager/components/sidebar/StatusButton.tsx b/code/core/src/manager/components/sidebar/StatusButton.tsx index 86216a3bf49b..fd0651dbeb2a 100644 --- a/code/core/src/manager/components/sidebar/StatusButton.tsx +++ b/code/core/src/manager/components/sidebar/StatusButton.tsx @@ -18,8 +18,9 @@ const withStatusColor = ({ theme, status }: { theme: Theme; status: StatusValue color: { 'status-value:pending': defaultColor, 'status-value:success': theme.color.positive, - 'status-value:new': theme.color.positive, - 'status-value:modified': theme.color.secondary, + 'status-value:new': theme.fgColor.accent, + 'status-value:modified': theme.fgColor.accent, + 'status-value:affected': theme.fgColor.accent, 'status-value:error': theme.color.negative, 'status-value:warning': theme.color.warning, 'status-value:unknown': defaultColor, diff --git a/code/core/src/manager/components/sidebar/StatusContext.tsx b/code/core/src/manager/components/sidebar/StatusContext.tsx index a916bfe808a3..5a4f72658765 100644 --- a/code/core/src/manager/components/sidebar/StatusContext.tsx +++ b/code/core/src/manager/components/sidebar/StatusContext.tsx @@ -29,6 +29,7 @@ export const useStatusSummary = (item: Item) => { 'status-value:success': 0, 'status-value:new': 0, 'status-value:modified': 0, + 'status-value:affected': 0, 'status-value:error': 0, 'status-value:warning': 0, 'status-value:unknown': 0, @@ -38,6 +39,7 @@ export const useStatusSummary = (item: Item) => { 'status-value:success': {}, 'status-value:new': {}, 'status-value:modified': {}, + 'status-value:affected': {}, 'status-value:error': {}, 'status-value:warning': {}, 'status-value:unknown': {}, diff --git a/code/core/src/manager/utils/status.tsx b/code/core/src/manager/utils/status.tsx index 72f07d7c97bc..594442174939 100644 --- a/code/core/src/manager/utils/status.tsx +++ b/code/core/src/manager/utils/status.tsx @@ -29,8 +29,9 @@ export const statusPriority: StatusValue[] = [ 'status-value:unknown', 'status-value:pending', 'status-value:success', - 'status-value:new', 'status-value:modified', + 'status-value:affected', + 'status-value:new', 'status-value:warning', 'status-value:error', ]; @@ -60,6 +61,12 @@ export const getStatus = memoizerific(5)((theme: Theme, status: StatusValue) => , theme.color.secondary, ], + ['status-value:affected']: [ + + + , + theme.color.warning, + ], ['status-value:warning']: [ diff --git a/code/core/src/shared/status-store/index.ts b/code/core/src/shared/status-store/index.ts index 5631c38a45a7..b65c4064f444 100644 --- a/code/core/src/shared/status-store/index.ts +++ b/code/core/src/shared/status-store/index.ts @@ -11,6 +11,7 @@ export type StatusValue = | 'status-value:success' | 'status-value:new' | 'status-value:modified' + | 'status-value:affected' | 'status-value:warning' | 'status-value:error' | 'status-value:unknown'; From a5726369e67ada316a55c817db65703faa309314 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Tue, 24 Mar 2026 20:37:48 +0100 Subject: [PATCH 3/8] Fix tests --- code/core/src/manager/utils/status.test.ts | 46 +++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/code/core/src/manager/utils/status.test.ts b/code/core/src/manager/utils/status.test.ts index 462650c8dbba..ff27c2d69cf4 100644 --- a/code/core/src/manager/utils/status.test.ts +++ b/code/core/src/manager/utils/status.test.ts @@ -36,13 +36,27 @@ describe('getHighestStatus', () => { 'status-value:modified', 'status-value:success', ]) - ).toBe('status-value:modified'); + ).toBe('status-value:new'); }); it('should rank warning above new', () => { expect(getMostCriticalStatusValue(['status-value:new', 'status-value:warning'])).toBe( 'status-value:warning' ); }); + + it('should rank affected above modified and below warning', () => { + expect( + getMostCriticalStatusValue([ + 'status-value:affected', + 'status-value:modified', + 'status-value:success', + ]) + ).toBe('status-value:affected'); + + expect(getMostCriticalStatusValue(['status-value:modified', 'status-value:warning'])).toBe( + 'status-value:warning' + ); + }); }); describe('getGroupStatus', () => { @@ -146,4 +160,34 @@ describe('getGroupStatus', () => { } `); }); + it('should propagate status-value:affected through group aggregation', () => { + expect( + getGroupStatus(mockDataset.withRoot, { + 'group-1--child-b1': { + a: { + storyId: 'group-1--child-b1', + typeId: 'a', + value: 'status-value:affected', + description: '', + title: '', + }, + }, + }) + ).toMatchInlineSnapshot(` + { + "group-1": "status-value:affected", + "group-1--child-b1": "status-value:unknown", + "group-1--child-b2": "status-value:unknown", + "root-1-child-a1": "status-value:unknown", + "root-1-child-a2": "status-value:unknown", + "root-1-child-a2--grandchild-a1-1": "status-value:unknown", + "root-1-child-a2--grandchild-a1-1:test1": "status-value:unknown", + "root-1-child-a2--grandchild-a1-2": "status-value:unknown", + "root-3--child-a1": "status-value:unknown", + "root-3-child-a2": "status-value:unknown", + "root-3-child-a2--grandchild-a1-1": "status-value:unknown", + "root-3-child-a2--grandchild-a1-2": "status-value:unknown", + } + `); + }); }); From 40a470ee497cc231e42ea34a01b8955e7d0916a1 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 25 Mar 2026 09:37:41 +0100 Subject: [PATCH 4/8] extract test provider status values --- code/addons/vitest/src/use-test-provider-state.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/code/addons/vitest/src/use-test-provider-state.ts b/code/addons/vitest/src/use-test-provider-state.ts index 2d96246632e2..1d2eaf787c65 100644 --- a/code/addons/vitest/src/use-test-provider-state.ts +++ b/code/addons/vitest/src/use-test-provider-state.ts @@ -20,7 +20,12 @@ import { import { ADDON_ID, STATUS_TYPE_ID_A11Y, STATUS_TYPE_ID_COMPONENT_TEST } from './constants'; import type { StoreState } from './types'; -export type StatusValueToStoryIds = Record; +type TestStatusValue = Extract< + StatusValue, + `status-value:${'pending' | 'success' | 'error' | 'warning' | 'unknown'}` +>; + +export type StatusValueToStoryIds = Record; const statusValueToStoryIds = ( allStatuses: StatusesByStoryIdAndTypeId, @@ -30,9 +35,6 @@ const statusValueToStoryIds = ( const statusValueToStoryIdsMap: StatusValueToStoryIds = { 'status-value:pending': [], 'status-value:success': [], - 'status-value:new': [], - 'status-value:modified': [], - 'status-value:affected': [], 'status-value:error': [], 'status-value:warning': [], 'status-value:unknown': [], @@ -46,7 +48,7 @@ const statusValueToStoryIds = ( if (!status) { return; } - statusValueToStoryIdsMap[status.value].push(status.storyId); + statusValueToStoryIdsMap[status.value as TestStatusValue].push(status.storyId); }); return statusValueToStoryIdsMap; From 5f2595642256c4bf8eef58a015cd2c8dac5beac9 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 25 Mar 2026 10:57:53 +0100 Subject: [PATCH 5/8] Enhance status value handling and update related components for change detection --- .../components/sidebar/IconSymbols.tsx | 62 +++++---- .../components/sidebar/Sidebar.stories.tsx | 129 +++++++++++++++++- .../components/sidebar/StatusContext.tsx | 11 +- .../src/manager/components/sidebar/Tree.tsx | 31 +++-- code/core/src/manager/utils/status.tsx | 8 +- .../e2e-tests/component-testing.spec.ts | 24 ++-- .../react/e2e-tests/component-testing.spec.ts | 28 ++-- 7 files changed, 224 insertions(+), 69 deletions(-) diff --git a/code/core/src/manager/components/sidebar/IconSymbols.tsx b/code/core/src/manager/components/sidebar/IconSymbols.tsx index e2d4b6fe8e75..c5a9c9b842ee 100644 --- a/code/core/src/manager/components/sidebar/IconSymbols.tsx +++ b/code/core/src/manager/components/sidebar/IconSymbols.tsx @@ -108,42 +108,50 @@ export const IconSymbols: FC = () => { - {/* https://github.com/storybookjs/icons/blob/main/src/icons/AddIcon.tsx */} - - - {/* https://github.com/storybookjs/icons/blob/main/src/icons/EditIcon.tsx */} - - - {/* Ripple/wave icon for affected stories */} + + + + + + + + diff --git a/code/core/src/manager/components/sidebar/Sidebar.stories.tsx b/code/core/src/manager/components/sidebar/Sidebar.stories.tsx index eaa7908c500a..03b49800cc78 100644 --- a/code/core/src/manager/components/sidebar/Sidebar.stories.tsx +++ b/code/core/src/manager/components/sidebar/Sidebar.stories.tsx @@ -1,6 +1,10 @@ import React from 'react'; -import type { DecoratorFunction, StatusesByStoryIdAndTypeId } from 'storybook/internal/types'; +import type { + DecoratorFunction, + StatusValue, + StatusesByStoryIdAndTypeId, +} from 'storybook/internal/types'; import { global } from '@storybook/global'; @@ -530,3 +534,126 @@ export const Scrolled: Story = { await expect(scrollable.scrollTop).toBe(scrollable.scrollHeight - scrollable.clientHeight); }, }; + +export const StatusesNew: Story = { + args: { + allStatuses: Object.entries(index).reduce((acc, [id, item]) => { + if (item.type !== 'story') return acc; + return { + ...acc, + [id]: { + addonA: { + typeId: 'addonA', + storyId: id, + value: 'status-value:new' as StatusValue, + title: 'Change Detection', + description: 'This story is new', + }, + }, + } satisfies StatusesByStoryIdAndTypeId; + }, {} as StatusesByStoryIdAndTypeId), + }, + play: waitForChecklistWidget, +}; + +export const StatusesModified: Story = { + args: { + allStatuses: Object.entries(index).reduce((acc, [id, item]) => { + if (item.type !== 'story') return acc; + return { + ...acc, + [id]: { + addonA: { + typeId: 'addonA', + storyId: id, + value: 'status-value:modified' as StatusValue, + title: 'Change Detection', + description: 'This story was modified', + }, + }, + } satisfies StatusesByStoryIdAndTypeId; + }, {} as StatusesByStoryIdAndTypeId), + }, + play: waitForChecklistWidget, +}; + +export const StatusesAffected: Story = { + args: { + allStatuses: Object.entries(index).reduce((acc, [id, item]) => { + if (item.type !== 'story') return acc; + return { + ...acc, + [id]: { + addonA: { + typeId: 'addonA', + storyId: id, + value: 'status-value:affected' as StatusValue, + title: 'Change Detection', + description: 'This story is affected by a change', + }, + }, + } satisfies StatusesByStoryIdAndTypeId; + }, {} as StatusesByStoryIdAndTypeId), + }, + play: waitForChecklistWidget, +}; + +export const StatusesMixed: Story = { + args: { + allStatuses: Object.entries(index).reduce((acc, [id, item]) => { + if (item.type !== 'story') return acc; + const values: StatusValue[] = [ + 'status-value:new', + 'status-value:modified', + 'status-value:affected', + 'status-value:success', + 'status-value:warning', + ]; + const value = values[Object.keys(acc).length % values.length]; + return { + ...acc, + [id]: { + addonA: { + typeId: 'addonA', + storyId: id, + value, + title: 'Change Detection', + description: '', + }, + }, + } satisfies StatusesByStoryIdAndTypeId; + }, {} as StatusesByStoryIdAndTypeId), + }, + play: waitForChecklistWidget, +}; + +export const StatusesChangeDetectionPriority: Story = { + args: { + allStatuses: Object.entries(index).reduce((acc, [id, item]) => { + if (item.type !== 'story') return acc; + // Cycles through all change-detection variants + warning/error to verify + // priority ordering (most critical wins): error > warning > affected > modified > new + const priorityValues: StatusValue[] = [ + 'status-value:new', + 'status-value:modified', + 'status-value:affected', + 'status-value:warning', + 'status-value:error', + ]; + const value = priorityValues[Object.keys(acc).length % priorityValues.length]; + return { + ...acc, + [id]: { + addonA: { + typeId: 'addonA', + storyId: id, + value, + title: 'Change Detection', + description: `Priority test: ${value}`, + }, + }, + } satisfies StatusesByStoryIdAndTypeId; + }, {} as StatusesByStoryIdAndTypeId), + }, + play: waitForChecklistWidget, +}; diff --git a/code/core/src/manager/components/sidebar/StatusContext.tsx b/code/core/src/manager/components/sidebar/StatusContext.tsx index 5a4f72658765..5b5759e91fa8 100644 --- a/code/core/src/manager/components/sidebar/StatusContext.tsx +++ b/code/core/src/manager/components/sidebar/StatusContext.tsx @@ -50,9 +50,14 @@ export const useStatusSummary = (item: Item) => { data && allStatuses && groupStatus && - ['status-value:pending', 'status-value:warning', 'status-value:error'].includes( - groupStatus[item.id] - ) + [ + 'status-value:pending', + 'status-value:new', + 'status-value:modified', + 'status-value:affected', + 'status-value:warning', + 'status-value:error', + ].includes(groupStatus[item.id]) ) { for (const storyId of getDescendantIds(data, item.id, false)) { for (const status of Object.values(allStatuses[storyId] ?? {})) { diff --git a/code/core/src/manager/components/sidebar/Tree.tsx b/code/core/src/manager/components/sidebar/Tree.tsx index c2dd3fded13e..bec864c177e5 100644 --- a/code/core/src/manager/components/sidebar/Tree.tsx +++ b/code/core/src/manager/components/sidebar/Tree.tsx @@ -44,7 +44,7 @@ import { } from '../../utils/tree'; import { useLayout } from '../layout/LayoutProvider'; import { useContextMenu } from './ContextMenu'; -import { IconSymbols, UseSymbol } from './IconSymbols'; +import { UseSymbol } from './IconSymbols'; import { StatusButton } from './StatusButton'; import { StatusContext } from './StatusContext'; import { @@ -190,6 +190,9 @@ const StatusIconMap: Record = { 'status-value:error': , 'status-value:warning': , 'status-value:pending': , + 'status-value:new': null, + 'status-value:modified': null, + 'status-value:affected': null, 'status-value:unknown': null, }; @@ -308,7 +311,7 @@ const Node = React.memo(function Node(props) { {contextMenu.node} {icon ? ( (function Node(props) { const [itemIcon, itemColor] = getStatus(theme, itemStatus); const itemStatusButton = itemIcon ? ( (function Node(props) { ]; const status = getMostCriticalStatusValue([itemStatus, groupStatus?.[item.id]]); const color = status ? getStatus(theme, status)[1] : null; - const showBranchStatus = status === 'status-value:error' || status === 'status-value:warning'; + const showBranchStatus = ( + [ + 'status-value:modified', + 'status-value:affected', + 'status-value:new', + 'status-value:warning', + 'status-value:error', + ] as StatusValue[] + ).includes(status); return ( (function Node(props) { {contextMenu.node} {showBranchStatus ? ( - - - + {status === 'status-value:error' || status === 'status-value:warning' ? ( + + + + ) : ( + getStatus(theme, status)[0] + )} ) : ( itemStatusButton diff --git a/code/core/src/manager/utils/status.tsx b/code/core/src/manager/utils/status.tsx index 594442174939..bd14bbab25a3 100644 --- a/code/core/src/manager/utils/status.tsx +++ b/code/core/src/manager/utils/status.tsx @@ -29,8 +29,8 @@ export const statusPriority: StatusValue[] = [ 'status-value:unknown', 'status-value:pending', 'status-value:success', - 'status-value:modified', 'status-value:affected', + 'status-value:modified', 'status-value:new', 'status-value:warning', 'status-value:error', @@ -53,19 +53,19 @@ export const getStatus = memoizerific(5)((theme: Theme, status: StatusValue) => , - theme.color.positive, + null, ], ['status-value:modified']: [ , - theme.color.secondary, + null, ], ['status-value:affected']: [ , - theme.color.warning, + null, ], ['status-value:warning']: [ diff --git a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts index c6a111fb49fc..51652f28d04f 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react-vitest-3/e2e-tests/component-testing.spec.ts @@ -186,7 +186,7 @@ test.describe("component testing", () => { ); await expect(failingStoryElement).toHaveAttribute( "aria-label", - "Test status: success" + "Status: success" ); await expect(sbPage.panelContent()).toContainText( /This interaction test passed in the CLI, but the tests failed in this browser/ @@ -199,7 +199,7 @@ test.describe("component testing", () => { ); await expect(successfulStoryElement).toHaveAttribute( "aria-label", - "Test status: error" + "Status: error" ); await expect(sbPage.panelContent()).toContainText( /This interaction test passed in this browser, but the tests failed in the CLI/ @@ -257,7 +257,7 @@ test.describe("component testing", () => { ); await expect(successfulStoryElement).toHaveAttribute( "aria-label", - "Test status: success" + "Status: success" ); // Assert for expected failure @@ -266,7 +266,7 @@ test.describe("component testing", () => { ); await expect(failingStoryElement).toHaveAttribute( "aria-label", - "Test status: error" + "Status: error" ); // Assert that filter works as intended @@ -316,7 +316,7 @@ test.describe("component testing", () => { ); await expect(successfulStoryElement).toHaveAttribute( "aria-label", - "Test status: success" + "Status: success" ); // Assert for expected failure @@ -325,7 +325,7 @@ test.describe("component testing", () => { ); await expect(failingStoryElement).toHaveAttribute( "aria-label", - "Test status: error" + "Status: error" ); // Assert that filter works as intended @@ -372,7 +372,7 @@ test.describe("component testing", () => { ); await expect(failingStoryElement).toHaveAttribute( "aria-label", - "Test status: error" + "Status: error" ); }); @@ -412,7 +412,7 @@ test.describe("component testing", () => { await expect(failingStoryElement).toHaveAttribute( "aria-label", - "Test status: error" + "Status: error" ); }); @@ -548,7 +548,7 @@ test.describe("component testing", () => { await page.click("body"); await expect( page.locator( - '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: success"]' + '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Status: success"]' ) ).toHaveCount(1); }); @@ -661,12 +661,12 @@ test.describe("component testing", () => { await page.click("body"); await expect( page.locator( - '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: success"]' + '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Status: success"]' ) ).toHaveCount(8); await expect( page.locator( - '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: error"]' + '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Status: error"]' ) ).toHaveCount(3); }); @@ -722,7 +722,7 @@ test.describe("component testing", () => { ); await expect( page.locator( - '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: error"]' + '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Status: error"]' ) ).toHaveCount(4); // 1 visible/expanded story, 1 expanded component, 1 collapsed component, 1 group }); diff --git a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts index e996e3433df0..caa156e6175d 100644 --- a/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts +++ b/test-storybooks/portable-stories-kitchen-sink/react/e2e-tests/component-testing.spec.ts @@ -182,7 +182,7 @@ test.describe("component testing", () => { ); await expect(failingStoryElement).toHaveAttribute( "aria-label", - "Test status: success" + "Status: success" ); await expect(sbPage.panelContent()).toContainText( /This interaction test passed in the CLI, but the tests failed in this browser/ @@ -195,7 +195,7 @@ test.describe("component testing", () => { ); await expect(successfulStoryElement).toHaveAttribute( "aria-label", - "Test status: error" + "Status: error" ); await expect(sbPage.panelContent()).toContainText( /This interaction test passed in this browser, but the tests failed in the CLI/ @@ -253,7 +253,7 @@ test.describe("component testing", () => { ); await expect(successfulStoryElement).toHaveAttribute( "aria-label", - "Test status: success" + "Status: success" ); // Assert for expected failure @@ -262,7 +262,7 @@ test.describe("component testing", () => { ); await expect(failingStoryElement).toHaveAttribute( "aria-label", - "Test status: error" + "Status: error" ); // Assert that filter works as intended @@ -312,7 +312,7 @@ test.describe("component testing", () => { ); await expect(successfulStoryElement).toHaveAttribute( "aria-label", - "Test status: success" + "Status: success" ); // Assert for expected failure @@ -321,7 +321,7 @@ test.describe("component testing", () => { ); await expect(failingStoryElement).toHaveAttribute( "aria-label", - "Test status: error" + "Status: error" ); // Assert that filter works as intended @@ -368,7 +368,7 @@ test.describe("component testing", () => { ); await expect(failingStoryElement).toHaveAttribute( "aria-label", - "Test status: error" + "Status: error" ); }); @@ -408,7 +408,7 @@ test.describe("component testing", () => { await expect(failingStoryElement).toHaveAttribute( "aria-label", - "Test status: error" + "Status: error" ); }); @@ -544,7 +544,7 @@ test.describe("component testing", () => { await page.click("body"); await expect( page.locator( - '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: success"]' + '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Status: success"]' ) ).toHaveCount(1); }); @@ -655,12 +655,12 @@ test.describe("component testing", () => { await page.click("body"); await expect( page.locator( - '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: success"]' + '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Status: success"]' ) ).toHaveCount(8); await expect( page.locator( - '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: error"]' + '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Status: error"]' ) ).toHaveCount(3); // 1 story, 1 component, 1 group }); @@ -716,19 +716,19 @@ test.describe("component testing", () => { ); await expect( page.locator( - '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: error"]' + '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Status: error"]' ) ).toHaveCount(4); // 1 visible/expanded story, 1 expanded component, 1 collapsed component, 1 group await page.click("body"); await expect( page.locator( - '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: success"]' + '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Status: success"]' ) ).toHaveCount(8); await expect( page.locator( - '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Test status: error"]' + '#storybook-explorer-menu [data-testid="tree-status-button"][aria-label="Status: error"]' ) ).toHaveCount(4); }); From 080269278db051133b88871fb02b083334a39447 Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 25 Mar 2026 11:02:34 +0100 Subject: [PATCH 6/8] Remove change detection --- code/core/src/types/modules/core-common.ts | 7 ------- 1 file changed, 7 deletions(-) diff --git a/code/core/src/types/modules/core-common.ts b/code/core/src/types/modules/core-common.ts index ce41760098f0..5f6339dd09e8 100644 --- a/code/core/src/types/modules/core-common.ts +++ b/code/core/src/types/modules/core-common.ts @@ -535,13 +535,6 @@ export interface StorybookConfigRaw { * @experimental This feature is in early development and may change significantly in future releases. */ experimentalCodeExamples?: boolean; - - /** - * Enable change detection for agentic UI review. - * - * @default true - */ - changeDetection?: boolean; }; build?: TestBuildConfig; From 49b23fd053d8197e83b2bad02c3f364048717c9e Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 25 Mar 2026 11:17:04 +0100 Subject: [PATCH 7/8] Fix tests --- code/core/src/manager/utils/status.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/manager/utils/status.test.ts b/code/core/src/manager/utils/status.test.ts index ff27c2d69cf4..f5f2ef80a56f 100644 --- a/code/core/src/manager/utils/status.test.ts +++ b/code/core/src/manager/utils/status.test.ts @@ -51,7 +51,7 @@ describe('getHighestStatus', () => { 'status-value:modified', 'status-value:success', ]) - ).toBe('status-value:affected'); + ).toBe('status-value:modified'); expect(getMostCriticalStatusValue(['status-value:modified', 'status-value:warning'])).toBe( 'status-value:warning' From 0b1442a14b6b73770d8349a307e1eb051e82b46d Mon Sep 17 00:00:00 2001 From: Valentin Palkovic Date: Wed, 25 Mar 2026 14:14:01 +0100 Subject: [PATCH 8/8] Fix test description --- code/core/src/manager/utils/status.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/code/core/src/manager/utils/status.test.ts b/code/core/src/manager/utils/status.test.ts index f5f2ef80a56f..a73959f42c1f 100644 --- a/code/core/src/manager/utils/status.test.ts +++ b/code/core/src/manager/utils/status.test.ts @@ -44,7 +44,7 @@ describe('getHighestStatus', () => { ); }); - it('should rank affected above modified and below warning', () => { + it('should rank affected below modified and below warning', () => { expect( getMostCriticalStatusValue([ 'status-value:affected',