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 (