From 19bf32e7b9055ba538ad68b5e08f0b185933292d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 22:11:40 +0000 Subject: [PATCH 1/5] Initial plan From dcf1a0717fd9068d27e898a926cbc1c63167c447 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 22:14:19 +0000 Subject: [PATCH 2/5] Add Storybook stories for feature selector components Agent-Logs-Url: https://github.com/Automattic/jetpack/sessions/c03948c1-2e59-47c7-8a0b-d608e8a2a164 Co-authored-by: adamwoodnz <1017872+adamwoodnz@users.noreply.github.com> --- .../js-packages/storybook/storybook/main.js | 4 +- .../stories/experience-option.stories.jsx | 179 ++++++++++++++++ .../stories/feature-selector.stories.jsx | 193 ++++++++++++++++++ .../packages/search/src/dashboard/dummy.js | 11 + 4 files changed, 386 insertions(+), 1 deletion(-) create mode 100644 projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx create mode 100644 projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx create mode 100644 projects/packages/search/src/dashboard/dummy.js diff --git a/projects/js-packages/storybook/storybook/main.js b/projects/js-packages/storybook/storybook/main.js index 50742bc60808..194e22a0c913 100644 --- a/projects/js-packages/storybook/storybook/main.js +++ b/projects/js-packages/storybook/storybook/main.js @@ -83,7 +83,9 @@ const sbconfig = { name: 'search-dashboard-modules', async resolveId( id, importer ) { if ( - id.startsWith( 'components/' ) && + ( id.startsWith( 'components/' ) || + id === 'store' || + id.startsWith( 'store/' ) ) && importer?.includes( '/search/src/dashboard/' ) ) { const dummyFile = path.join( diff --git a/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx b/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx new file mode 100644 index 000000000000..59afe987c916 --- /dev/null +++ b/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx @@ -0,0 +1,179 @@ +import { createReduxStore, createRegistry, RegistryProvider } from '@wordpress/data'; +import ExperienceOption from '../experience-option'; +import { storeConfig, STORE_ID } from '../../../store'; +import { EXPERIENCE } from '../constants'; + +export default { + title: 'Packages/Search/FeatureSelector/ExperienceOption', + component: ExperienceOption, + parameters: { + layout: 'centered', + }, + decorators: [ + Story => ( +
+ +
+ ), + ], + argTypes: { + experience: { + control: 'select', + options: [ 'embedded', 'overlay', 'inline', 'off' ], + }, + disabled: { + control: 'boolean', + }, + }, +}; + +const createStoreWithSettings = jetpackSettings => { + const registry = createRegistry(); + const store = createReduxStore( STORE_ID, { + ...storeConfig, + initialState: { ...( storeConfig.initialState || {} ), jetpackSettings }, + } ); + registry.register( store ); + return registry; +}; + +const Template = args => { + const baseSettings = { + module_active: true, + instant_search_enabled: true, + pending_experience: args.pendingExperience || null, + experience: args.activeExperience || null, + }; + const registry = createStoreWithSettings( baseSettings ); + return ( + + + + ); +}; + +// Embedded experience (default - shows RECOMMENDED badge) +export const Embedded = Template.bind( {} ); +Embedded.args = { + experience: EXPERIENCE.EMBEDDED, + disabled: false, + pendingExperience: null, + activeExperience: null, +}; + +// Embedded selected +export const EmbeddedSelected = Template.bind( {} ); +EmbeddedSelected.args = { + experience: EXPERIENCE.EMBEDDED, + disabled: false, + pendingExperience: EXPERIENCE.EMBEDDED, + activeExperience: null, +}; + +// Embedded active (shows ACTIVE badge) +export const EmbeddedActive = Template.bind( {} ); +EmbeddedActive.args = { + experience: EXPERIENCE.EMBEDDED, + disabled: false, + pendingExperience: null, + activeExperience: EXPERIENCE.EMBEDDED, +}; + +// Overlay experience +export const Overlay = Template.bind( {} ); +Overlay.args = { + experience: EXPERIENCE.OVERLAY, + disabled: false, + pendingExperience: null, + activeExperience: null, +}; + +// Overlay selected +export const OverlaySelected = Template.bind( {} ); +OverlaySelected.args = { + experience: EXPERIENCE.OVERLAY, + disabled: false, + pendingExperience: EXPERIENCE.OVERLAY, + activeExperience: null, +}; + +// Overlay active (instant_search_enabled=true maps to overlay) +export const OverlayActive = Template.bind( {} ); +OverlayActive.args = { + experience: EXPERIENCE.OVERLAY, + disabled: false, + pendingExperience: null, + activeExperience: EXPERIENCE.OVERLAY, +}; + +// Theme search (inline) experience +export const Inline = Template.bind( {} ); +Inline.args = { + experience: EXPERIENCE.INLINE, + disabled: false, + pendingExperience: null, + activeExperience: null, +}; + +// Inline selected +export const InlineSelected = Template.bind( {} ); +InlineSelected.args = { + experience: EXPERIENCE.INLINE, + disabled: false, + pendingExperience: EXPERIENCE.INLINE, + activeExperience: null, +}; + +// Inline active +export const InlineActive = Template.bind( {} ); +InlineActive.args = { + experience: EXPERIENCE.INLINE, + disabled: false, + pendingExperience: null, + activeExperience: EXPERIENCE.INLINE, +}; + +// Off experience +export const Off = Template.bind( {} ); +Off.args = { + experience: EXPERIENCE.OFF, + disabled: false, + pendingExperience: null, + activeExperience: null, +}; + +// Off selected +export const OffSelected = Template.bind( {} ); +OffSelected.args = { + experience: EXPERIENCE.OFF, + disabled: false, + pendingExperience: EXPERIENCE.OFF, + activeExperience: null, +}; + +// Off active +export const OffActive = Template.bind( {} ); +OffActive.args = { + experience: EXPERIENCE.OFF, + disabled: false, + pendingExperience: null, + activeExperience: EXPERIENCE.OFF, +}; + +// Disabled experience (shows upgrade tooltip) +export const Disabled = Template.bind( {} ); +Disabled.args = { + experience: EXPERIENCE.EMBEDDED, + disabled: true, + pendingExperience: null, + activeExperience: null, +}; + +// Unselected state +export const Unselected = Template.bind( {} ); +Unselected.args = { + experience: EXPERIENCE.INLINE, + disabled: false, + pendingExperience: EXPERIENCE.EMBEDDED, + activeExperience: null, +}; diff --git a/projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx b/projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx new file mode 100644 index 000000000000..6ea0d7734b61 --- /dev/null +++ b/projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx @@ -0,0 +1,193 @@ +import { createReduxStore, createRegistry, RegistryProvider } from '@wordpress/data'; +import FeatureSelector from '../index'; +import { storeConfig, STORE_ID } from '../../../store'; +import { EXPERIENCE } from '../constants'; + +export default { + title: 'Packages/Search/FeatureSelector', + component: FeatureSelector, + parameters: { + layout: 'centered', + }, + decorators: [ + Story => ( +
+ +
+ ), + ], +}; + +const createStoreWithSettings = ( jetpackSettings, sitePlan = {}, siteData = {} ) => { + const registry = createRegistry(); + const store = createReduxStore( STORE_ID, { + ...storeConfig, + initialState: { + ...( storeConfig.initialState || {} ), + jetpackSettings, + sitePlan, + siteData, + }, + } ); + registry.register( store ); + return registry; +}; + +// Clean state - Save button is aria-disabled (no changes made) +export const Clean = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: null, + experience: EXPERIENCE.OVERLAY, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Dirty state - Save button enabled (user selected a different experience) +export const Dirty = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: EXPERIENCE.INLINE, + experience: EXPERIENCE.OVERLAY, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Saving state - Save button shows loading spinner +export const Saving = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: EXPERIENCE.INLINE, + experience: EXPERIENCE.OVERLAY, + is_updating: true, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Classic-only plan - Embedded and Overlay rows are disabled +export const ClassicOnlyPlan = () => { + const settings = { + module_active: true, + instant_search_enabled: false, + pending_experience: null, + experience: EXPERIENCE.INLINE, + is_updating: false, + }; + const sitePlan = { + supports_only_classic_search: true, + }; + const registry = createStoreWithSettings( settings, sitePlan ); + return ( + + + + ); +}; + +// Embedded experience active +export const EmbeddedActive = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: null, + experience: EXPERIENCE.EMBEDDED, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Overlay experience active +export const OverlayActive = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: null, + experience: EXPERIENCE.OVERLAY, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Theme search (inline) experience active +export const InlineActive = () => { + const settings = { + module_active: true, + instant_search_enabled: false, + pending_experience: null, + experience: EXPERIENCE.INLINE, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// Off experience active +export const OffActive = () => { + const settings = { + module_active: false, + instant_search_enabled: false, + pending_experience: null, + experience: EXPERIENCE.OFF, + is_updating: false, + }; + const registry = createStoreWithSettings( settings ); + return ( + + + + ); +}; + +// WordPress.com site - Off option is hidden +export const WpcomSite = () => { + const settings = { + module_active: true, + instant_search_enabled: true, + pending_experience: null, + experience: EXPERIENCE.OVERLAY, + is_updating: false, + }; + const siteData = { + isWpcom: true, + }; + const registry = createStoreWithSettings( settings, {}, siteData ); + return ( + + + + ); +}; diff --git a/projects/packages/search/src/dashboard/dummy.js b/projects/packages/search/src/dashboard/dummy.js new file mode 100644 index 000000000000..d9d8ef4f6c15 --- /dev/null +++ b/projects/packages/search/src/dashboard/dummy.js @@ -0,0 +1,11 @@ +/** + * Dummy file used by the Storybook Vite plugin (`search-dashboard-modules`) + * to resolve bare `components/` and `store` imports relative to the dashboard + * directory. + * + * The plugin intercepts imports like `import { STORE_ID } from 'store';` in + * dashboard components and resolves them as `./store` relative to this file's + * location, so they correctly point to `src/dashboard/store/index.js`. + * + * See: projects/js-packages/storybook/storybook/main.js + */ From 9d258d096c8a90fc308304b522300a6a54d7c2eb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 7 May 2026 22:17:32 +0000 Subject: [PATCH 3/5] Add changelog entry for Storybook stories Agent-Logs-Url: https://github.com/Automattic/jetpack/sessions/c03948c1-2e59-47c7-8a0b-d608e8a2a164 --- .../search/changelog/add-storybook-stories-search-179 | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/packages/search/changelog/add-storybook-stories-search-179 diff --git a/projects/packages/search/changelog/add-storybook-stories-search-179 b/projects/packages/search/changelog/add-storybook-stories-search-179 new file mode 100644 index 000000000000..e089dbb9da53 --- /dev/null +++ b/projects/packages/search/changelog/add-storybook-stories-search-179 @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Add Storybook stories for feature-selector components (ExperienceOption and FeatureSelector). From 02e6047ecf0a8b5a3cf35b6084e3df8232e3b597 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Fri, 8 May 2026 10:37:56 +1200 Subject: [PATCH 4/5] Fix lint and add storybook changelog - Reformat the search-dashboard-modules id check to a single line (prettier). - Reorder imports in the new story files to satisfy import/order. - Add a js-packages/storybook changelog entry for the Vite plugin tweak. --- .../storybook/changelog/add-storybook-stories-search-179 | 4 ++++ projects/js-packages/storybook/storybook/main.js | 4 +--- .../feature-selector/stories/experience-option.stories.jsx | 2 +- .../feature-selector/stories/feature-selector.stories.jsx | 2 +- 4 files changed, 7 insertions(+), 5 deletions(-) create mode 100644 projects/js-packages/storybook/changelog/add-storybook-stories-search-179 diff --git a/projects/js-packages/storybook/changelog/add-storybook-stories-search-179 b/projects/js-packages/storybook/changelog/add-storybook-stories-search-179 new file mode 100644 index 000000000000..7ca0a2600590 --- /dev/null +++ b/projects/js-packages/storybook/changelog/add-storybook-stories-search-179 @@ -0,0 +1,4 @@ +Significance: patch +Type: changed + +Storybook Vite plugin: resolve bare `store` and `store/` imports for search dashboard stories. diff --git a/projects/js-packages/storybook/storybook/main.js b/projects/js-packages/storybook/storybook/main.js index 194e22a0c913..da5ffe9cdf7c 100644 --- a/projects/js-packages/storybook/storybook/main.js +++ b/projects/js-packages/storybook/storybook/main.js @@ -83,9 +83,7 @@ const sbconfig = { name: 'search-dashboard-modules', async resolveId( id, importer ) { if ( - ( id.startsWith( 'components/' ) || - id === 'store' || - id.startsWith( 'store/' ) ) && + ( id.startsWith( 'components/' ) || id === 'store' || id.startsWith( 'store/' ) ) && importer?.includes( '/search/src/dashboard/' ) ) { const dummyFile = path.join( diff --git a/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx b/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx index 59afe987c916..7d67ba8a14b2 100644 --- a/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx +++ b/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx @@ -1,7 +1,7 @@ import { createReduxStore, createRegistry, RegistryProvider } from '@wordpress/data'; -import ExperienceOption from '../experience-option'; import { storeConfig, STORE_ID } from '../../../store'; import { EXPERIENCE } from '../constants'; +import ExperienceOption from '../experience-option'; export default { title: 'Packages/Search/FeatureSelector/ExperienceOption', diff --git a/projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx b/projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx index 6ea0d7734b61..b4969d499062 100644 --- a/projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx +++ b/projects/packages/search/src/dashboard/components/feature-selector/stories/feature-selector.stories.jsx @@ -1,7 +1,7 @@ import { createReduxStore, createRegistry, RegistryProvider } from '@wordpress/data'; -import FeatureSelector from '../index'; import { storeConfig, STORE_ID } from '../../../store'; import { EXPERIENCE } from '../constants'; +import FeatureSelector from '../index'; export default { title: 'Packages/Search/FeatureSelector', From 2d186e43b854b75982b84eb4bd976768cfdd22b5 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Fri, 8 May 2026 16:41:41 +1200 Subject: [PATCH 5/5] Search 3.0 stories: pin a deterministic active experience per row The Template seed left settings.experience null and relied on the legacy boolean fallback in getActiveExperience, which derives 'overlay' from `instant_search_enabled: true`. That made the Overlay baseline and OverlaySelected stories render identically to OverlayActive (both is-selected and is-active set). Pick a default active that doesn't match the rendered row so each story shows the state it advertises. --- .../stories/experience-option.stories.jsx | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx b/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx index 7d67ba8a14b2..49a86ed2c511 100644 --- a/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx +++ b/projects/packages/search/src/dashboard/components/feature-selector/stories/experience-option.stories.jsx @@ -37,12 +37,20 @@ const createStoreWithSettings = jetpackSettings => { return registry; }; +// Pick a default `active` that won't accidentally match the rendered row, +// so baseline stories render as unselected/inactive unless `activeExperience` +// is set explicitly. Without this, the legacy boolean fallback in the +// `getActiveExperience` selector derives `'overlay'` from +// `instant_search_enabled: true` and contaminates every Overlay story. +const defaultActiveFor = experience => + experience === EXPERIENCE.EMBEDDED ? EXPERIENCE.INLINE : EXPERIENCE.EMBEDDED; + const Template = args => { const baseSettings = { module_active: true, instant_search_enabled: true, - pending_experience: args.pendingExperience || null, - experience: args.activeExperience || null, + pending_experience: args.pendingExperience ?? null, + experience: args.activeExperience ?? defaultActiveFor( args.experience ), }; const registry = createStoreWithSettings( baseSettings ); return (