From 6804a36657a5c90e79a058474b356210d9335528 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 21 Jun 2026 23:39:00 +0000 Subject: [PATCH 1/3] Premium Analytics: Add widget Storybook guidance and Overview clarifications to AGENTS.md Add a new ## Widgets section anchored to the widget layout introduced in #49505 (Average items per order): documents the required widgets//{package.json,widget.json,widget.ts,render.tsx,stories/} folder structure, the WidgetDashboardWithWidget-based story template, the Packages/Premium Analytics/Widgets/ title path, registerReportMocks() / autodocs requirements, the projects/js-packages/storybook/storybook/projects.js registration step, and widget-specific pitfalls (including avoiding the legacy packages/widgets-toolkit/src/widgets/* path and the legacy withWidgetRoot() decorator). Also restructure the existing ## Overview into a bulleted list with more detail on each consolidated surface (Odyssey dashboard / apps/odyssey-stats / email stats, and a link to the private woocommerce/woocommerce-analytics repo). Co-authored-by: Jasper Kang --- projects/packages/premium-analytics/AGENTS.md | 163 +++++++++++++++++- .../add-agents-md-widget-storybook-guidance | 5 + 2 files changed, 163 insertions(+), 5 deletions(-) create mode 100644 projects/packages/premium-analytics/changelog/add-agents-md-widget-storybook-guidance diff --git a/projects/packages/premium-analytics/AGENTS.md b/projects/packages/premium-analytics/AGENTS.md index c616dcdafd83..fd9e353467a3 100644 --- a/projects/packages/premium-analytics/AGENTS.md +++ b/projects/packages/premium-analytics/AGENTS.md @@ -4,11 +4,10 @@ Guidance for AI coding agents working in this package. ## Overview -Jetpack Premium Analytics is the unified analytics dashboard for Jetpack-connected sites — a -full-page React SPA in wp-admin. It is the successor to two older surfaces being consolidated -here: **Jetpack Stats** (`stats-admin` package, Odyssey dashboard — traffic, posts, -subscribers, WordAds, notices) and **Woo Analytics** (`woocommerce-analytics` package — store -reports: orders, products, customers, coupons, order attribution). +Jetpack Premium Analytics is the unified analytics dashboard for Jetpack-connected sites — a full-page React SPA in wp-admin. It consolidates two older surfaces: + +- **Jetpack Stats** — the Odyssey dashboard; backend from the `stats-admin` package, frontend built from `apps/odyssey-stats` in Calypso. Covers traffic, posts, subscribers, email stats, WordAds, and more. +- **Woo Analytics** — store reports (orders, products, customers, coupons, order attribution), from the private repo at https://github.com/woocommerce/woocommerce-analytics. - Composer package: `automattic/jetpack-premium-analytics` - PHP namespace: `Automattic\Jetpack\PremiumAnalytics` @@ -105,3 +104,157 @@ prefixes; Woo `analytics/reports/*` → `proxy/v2/analytics/reports/*`. The dash - `v2` vs `v1.x` changes the WPCOM base — a wrong version silently hits a different endpoint. - Sync code under `src/Sync/` is interim (WOOA7S-1550); don't build on it. - Don't edit dashboard React in Calypso — it lives here now. + +## Widgets + +New widgets live at the top of the package in `widgets//` and are composed from +primitives in `packages/widgets-toolkit/`. Each widget is its own pnpm workspace package so +its render bundle can be lazy-loaded by the dashboard at runtime. + +> **Legacy note.** Widgets currently under `packages/widgets-toolkit/src/widgets/*` (e.g. +> `sales-by-coupon`, `sales-by-utm`) predate this layout and are scheduled to be migrated. +> Do not use them as templates for new work — follow the structure below instead. The +> reference implementation is the **Average items per order** widget (added in +> [#49505](https://github.com/Automattic/jetpack/pull/49505)). + +### REQUIRED: widget folder structure + +Each new widget MUST ship as a self-contained folder with these files: + +```text +widgets// +├── package.json # workspace package; link: deps on widgets-toolkit +├── widget.json # declarative metadata (name, title, description, category) +├── widget.ts # runtime widget type definition (icon + translatable strings) +├── render.tsx # the React component, wrapped in from widgets-toolkit +└── stories/-widget.stories.tsx +``` + +Notes: + +- `name` in both `widget.json` and `widget.ts` MUST use the `jpa/` prefix + (e.g. `jpa/`). +- Keep `render.tsx` thin: compose toolkit primitives (`WidgetRoot`, + `OrderMetricWidget`, etc.) rather than reimplementing data fetching, chart wiring, or + theming. +- Per-widget React/`@wordpress/*` dependencies go in the widget's own `package.json` using + `link:` for internal packages (e.g. + `"@jetpack-premium-analytics/widgets-toolkit": "link:../../packages/widgets-toolkit"`). + +### REQUIRED: Storybook story for every widget + +Every widget MUST have a Storybook story alongside it. New widgets without a story should +not be merged. + +1. **Location**: `widgets//stories/-widget.stories.tsx`. +2. **Decorator**: Use `WidgetDashboardWithWidget` from + `widgets/stories/widget-dashboard-with-widget.tsx` as the rendered component. It mounts + the real `WidgetDashboard` with this single widget and exposes the standard dashboard + controls (size, edit mode, host environment, etc.) — wrapping the render output in an + ad-hoc div instead will not match how the widget actually renders in product. +3. **Mocks**: Call `registerReportMocks()` at module-level for any widget that fetches + report data. Without this the widget renders an error state in Storybook. +4. **Title**: `Packages/Premium Analytics/Widgets/` (note: no "Widgets Toolkit" + in the path — that path is reserved for the legacy widgets). +5. **Tags**: Include `tags: [ 'autodocs' ]` so the widget shows up in auto-generated docs. +6. **Storybook registration**: Add `projects/packages/premium-analytics/widgets` to + `projects/js-packages/storybook/storybook/projects.js` if it isn't there already. New + per-widget folders are picked up automatically once that root is registered. + +### Story template + +Mirror the Average items per order widget. Wrap the dashboard story in a thin component so +widget-specific args (e.g. comparison toggles, view selectors) become Storybook controls. + +```tsx +import { getDefaultQueryParams } from '@jetpack-premium-analytics/data'; +import { + DEFAULT_WIDGET_DASHBOARD_STORY_ARGS, + WidgetDashboardWithWidget as WidgetDashboardWithWidgetStory, + widgetDashboardWithWidgetArgTypes, + type WidgetDashboardWithWidgetControls, +} from '../../stories/widget-dashboard-with-widget'; +import { registerReportMocks } from '../../../packages/widgets-toolkit/src/stories/mocks/register-report-mocks'; +import MyWidgetRender from '../render'; +import widgetDefinition from '../widget'; +import type { Meta, StoryObj } from '@storybook/react'; +import type { WidgetRenderProps } from '@automattic/jetpack-widget-primitives'; +import type { ComponentType } from 'react'; + +registerReportMocks(); + +const MY_WIDGET_RENDER_MODULE = 'storybook/'; + +interface MyWidgetDashboardStoryProps extends WidgetDashboardWithWidgetControls { + withComparison: boolean; +} + +function MyWidgetDashboardStory( { + withComparison, + ...dashboardStoryArgs +}: MyWidgetDashboardStoryProps ) { + return ( + > } + attributes={ { + reportParams: getDefaultQueryParams( withComparison ), + } } + /> + ); +} + +const meta = { + title: 'Packages/Premium Analytics/Widgets/MyWidget', + component: MyWidgetDashboardStory, + tags: [ 'autodocs' ], + args: { + ...DEFAULT_WIDGET_DASHBOARD_STORY_ARGS, + withComparison: true, + }, + argTypes: { + ...widgetDashboardWithWidgetArgTypes, + withComparison: { + control: 'boolean', + }, + }, + parameters: { + docs: { + description: { + component: 'Brief description of what this widget shows and when to use it.', + }, + }, + }, +} satisfies Meta< typeof MyWidgetDashboardStory >; + +export default meta; + +type Story = StoryObj< typeof meta >; + +export const WidgetDashboardWithWidget: Story = {}; +``` + +Expose additional widget-specific props (e.g. a `view: 'source' | 'channel' | 'campaign'` +selector) as extra fields on the story-props interface plus matching `args` and `argTypes`. +The shared `WidgetDashboardWithWidget` decorator already provides container width / edit-mode +/ host-environment controls, so there's no need to add custom size decorators per widget. + +### Widget pitfalls + +- Putting new widgets under `packages/widgets-toolkit/src/widgets/*` — that path is for the + legacy widgets that haven't been migrated yet. +- Using the legacy `withWidgetRoot()` decorator for new stories — new widgets render via the + real `WidgetDashboard` through the shared story helper instead. +- Putting the story at the wrong title path (e.g. `Packages/Premium Analytics/Widgets + Toolkit/Widgets/` — that's the legacy path). +- Omitting `registerReportMocks()` at the top of the story file when the widget fetches + report data — the widget will render an error state. +- Forgetting to register `projects/packages/premium-analytics/widgets` in + `projects/js-packages/storybook/storybook/projects.js` — the story won't appear in + Storybook at all. +- Omitting `tags: [ 'autodocs' ]`, which silently drops the widget from the autogenerated + docs page. +- Reimplementing data fetching or chart wiring inside `render.tsx` instead of composing + primitives from `@jetpack-premium-analytics/widgets-toolkit`. diff --git a/projects/packages/premium-analytics/changelog/add-agents-md-widget-storybook-guidance b/projects/packages/premium-analytics/changelog/add-agents-md-widget-storybook-guidance new file mode 100644 index 000000000000..1ba1c95df4a6 --- /dev/null +++ b/projects/packages/premium-analytics/changelog/add-agents-md-widget-storybook-guidance @@ -0,0 +1,5 @@ +Significance: patch +Type: changed +Comment: Expand AGENTS.md with widget Storybook guidance and clarify the Jetpack Stats / Woo Analytics origins in the Overview. No user-facing change. + + From 926c6698d5526b524597c32bcdfb7318e72b8338 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Wed, 24 Jun 2026 12:29:19 +1200 Subject: [PATCH 2/3] Premium Analytics: make widget story guidance a self-contained template MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Rework the AGENTS.md story guidance so an agent can build all three required stories from the doc alone, without copying a specific widget's story file (which can drift). Inline snippets for each story — Default and WithComparison close-ups plus the WidgetDashboardWithWidget harness story — share one setup block. Reconcile the surrounding requirements: the dashboard story proves the in-product render while the close-ups use a plain canvas decorator, and the legacy note no longer points at an example widget as the thing to copy. Also corrects the WidgetRenderProps import, which pointed at a non-existent @automattic/jetpack-widget-primitives package; the real type comes from @wordpress/widget-primitives. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01P2vY2GxKEJurnaod7phrFo --- projects/packages/premium-analytics/AGENTS.md | 129 ++++++++++++------ 1 file changed, 89 insertions(+), 40 deletions(-) diff --git a/projects/packages/premium-analytics/AGENTS.md b/projects/packages/premium-analytics/AGENTS.md index fd9e353467a3..72a8f362dbec 100644 --- a/projects/packages/premium-analytics/AGENTS.md +++ b/projects/packages/premium-analytics/AGENTS.md @@ -113,9 +113,9 @@ its render bundle can be lazy-loaded by the dashboard at runtime. > **Legacy note.** Widgets currently under `packages/widgets-toolkit/src/widgets/*` (e.g. > `sales-by-coupon`, `sales-by-utm`) predate this layout and are scheduled to be migrated. -> Do not use them as templates for new work — follow the structure below instead. The -> reference implementation is the **Average items per order** widget (added in -> [#49505](https://github.com/Automattic/jetpack/pull/49505)). +> Do not use them as templates for new work — follow the structure and story template below +> instead. (This layout was first built for the **Average items per order** widget, +> [#49505](https://github.com/Automattic/jetpack/pull/49505).) ### REQUIRED: widget folder structure @@ -147,11 +147,12 @@ Every widget MUST have a Storybook story alongside it. New widgets without a sto not be merged. 1. **Location**: `widgets//stories/-widget.stories.tsx`. -2. **Decorator**: Use `WidgetDashboardWithWidget` from - `widgets/stories/widget-dashboard-with-widget.tsx` as the rendered component. It mounts - the real `WidgetDashboard` with this single widget and exposes the standard dashboard - controls (size, edit mode, host environment, etc.) — wrapping the render output in an - ad-hoc div instead will not match how the widget actually renders in product. +2. **Dashboard story**: Include a `WidgetDashboardWithWidget` story that renders through the + shared `WidgetDashboardWithWidget` helper from `widgets/stories/widget-dashboard-with-widget.tsx`. + It mounts the real `WidgetDashboard` with this single widget and exposes the standard + dashboard controls (size, edit mode, host environment, etc.), so it shows how the widget + actually renders in product. The `Default` / `WithComparison` close-up stories use the + simpler canvas decorator from the template below — but never ship *only* a bare-div story. 3. **Mocks**: Call `registerReportMocks()` at module-level for any widget that fetches report data. Without this the widget renders an error state in Storybook. 4. **Title**: `Packages/Premium Analytics/Widgets/` (note: no "Widgets Toolkit" @@ -163,8 +164,13 @@ not be merged. ### Story template -Mirror the Average items per order widget. Wrap the dashboard story in a thin component so -widget-specific args (e.g. comparison toggles, view selectors) become Storybook controls. +Every widget ships three stories: a **Default** close-up, a **WithComparison** close-up, and a +**WidgetDashboardWithWidget** story that mounts the real dashboard. This template is +self-contained — copy it as the base rather than an existing widget's story file, which may +have drifted. `meta.component` is the widget's render component; widget-specific args +(comparison toggles, view selectors, …) are wired as Storybook controls. + +The shared imports, helpers, and `meta`: ```tsx import { getDefaultQueryParams } from '@jetpack-premium-analytics/data'; @@ -177,48 +183,40 @@ import { import { registerReportMocks } from '../../../packages/widgets-toolkit/src/stories/mocks/register-report-mocks'; import MyWidgetRender from '../render'; import widgetDefinition from '../widget'; -import type { Meta, StoryObj } from '@storybook/react'; -import type { WidgetRenderProps } from '@automattic/jetpack-widget-primitives'; +import type { Decorator, Meta, StoryObj } from '@storybook/react'; +import type { WidgetRenderProps } from '@wordpress/widget-primitives'; import type { ComponentType } from 'react'; registerReportMocks(); const MY_WIDGET_RENDER_MODULE = 'storybook/'; -interface MyWidgetDashboardStoryProps extends WidgetDashboardWithWidgetControls { +// Widget-specific controls — add view selectors, metric toggles, etc. here. +interface MyWidgetStoryControls { withComparison: boolean; } -function MyWidgetDashboardStory( { - withComparison, - ...dashboardStoryArgs -}: MyWidgetDashboardStoryProps ) { +function renderMyWidget( { withComparison }: MyWidgetStoryControls ) { return ( - > } - attributes={ { - reportParams: getDefaultQueryParams( withComparison ), - } } + ); } +// Close-up canvas so the chart fills the frame outside the dashboard grid. +const withWidgetCanvas: Decorator = Story => ( +
+ +
+); + const meta = { title: 'Packages/Premium Analytics/Widgets/MyWidget', - component: MyWidgetDashboardStory, + component: MyWidgetRender, tags: [ 'autodocs' ], - args: { - ...DEFAULT_WIDGET_DASHBOARD_STORY_ARGS, - withComparison: true, - }, argTypes: { - ...widgetDashboardWithWidgetArgTypes, - withComparison: { - control: 'boolean', - }, + withComparison: { control: 'boolean' }, }, parameters: { docs: { @@ -227,19 +225,70 @@ const meta = { }, }, }, -} satisfies Meta< typeof MyWidgetDashboardStory >; +} satisfies Meta< MyWidgetStoryControls >; export default meta; -type Story = StoryObj< typeof meta >; +type Story = StoryObj< MyWidgetStoryControls >; +``` + +**1. `Default`** — the widget on its own, current period only: + +```tsx +export const Default: Story = { + render: renderMyWidget, + args: { withComparison: false }, + decorators: [ withWidgetCanvas ], +}; +``` + +**2. `WithComparison`** — same close-up with the period-over-period delta + sparkline: + +```tsx +export const WithComparison: Story = { + render: renderMyWidget, + args: { withComparison: true }, + decorators: [ withWidgetCanvas ], +}; +``` + +**3. `WidgetDashboardWithWidget`** — mounts the real `WidgetDashboard` so the widget renders +exactly as it does in product, inheriting the size / edit-mode / host-environment controls: + +```tsx +interface MyWidgetDashboardStoryProps + extends WidgetDashboardWithWidgetControls, + MyWidgetStoryControls {} + +function MyWidgetDashboardStory( { withComparison, ...dashboardArgs }: MyWidgetDashboardStoryProps ) { + return ( + > } + attributes={ { reportParams: getDefaultQueryParams( withComparison ) } } + /> + ); +} -export const WidgetDashboardWithWidget: Story = {}; +export const WidgetDashboardWithWidget: StoryObj< MyWidgetDashboardStoryProps > = { + render: args => , + args: { + ...DEFAULT_WIDGET_DASHBOARD_STORY_ARGS, + withComparison: true, + }, + argTypes: { + ...widgetDashboardWithWidgetArgTypes, + withComparison: { control: 'boolean' }, + }, +}; ``` Expose additional widget-specific props (e.g. a `view: 'source' | 'channel' | 'campaign'` -selector) as extra fields on the story-props interface plus matching `args` and `argTypes`. -The shared `WidgetDashboardWithWidget` decorator already provides container width / edit-mode -/ host-environment controls, so there's no need to add custom size decorators per widget. +selector) as extra fields on the controls interface plus matching `args` and `argTypes`. The +shared dashboard helper already provides container width / edit-mode / host-environment +controls, so there's no need to add custom size decorators per widget. ### Widget pitfalls From e8d15fdd976640f79f5abf012255a938cc830757 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Thu, 25 Jun 2026 10:01:32 +1200 Subject: [PATCH 3/3] Premium Analytics: address review feedback on widget AGENTS.md From nerrad's review (#4524224327): - Note widgets-toolkit primitives are built on @automattic/charts, and flag the toolkit as an interim layer whose module paths are provisional. - Drop the explicit #49505 link from the legacy note so it can't go stale. - Trim the widget pitfalls to the two non-obvious legacy traps; the rest just restated the REQUIRED folder-structure / story rules above. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01P2vY2GxKEJurnaod7phrFo --- projects/packages/premium-analytics/AGENTS.md | 23 +++++++------------ 1 file changed, 8 insertions(+), 15 deletions(-) diff --git a/projects/packages/premium-analytics/AGENTS.md b/projects/packages/premium-analytics/AGENTS.md index 72a8f362dbec..9baedbe906ee 100644 --- a/projects/packages/premium-analytics/AGENTS.md +++ b/projects/packages/premium-analytics/AGENTS.md @@ -108,14 +108,18 @@ prefixes; Woo `analytics/reports/*` → `proxy/v2/analytics/reports/*`. The dash ## Widgets New widgets live at the top of the package in `widgets//` and are composed from -primitives in `packages/widgets-toolkit/`. Each widget is its own pnpm workspace package so -its render bundle can be lazy-loaded by the dashboard at runtime. +primitives in `packages/widgets-toolkit/` — chart, metric, and layout components built on +`@automattic/charts`. Each widget is its own pnpm workspace package so its render bundle can be +lazy-loaded by the dashboard at runtime. + +> `packages/widgets-toolkit/` is an interim layer while the dashboard is in development and is +> expected to shrink over time (much of it folding into `@automattic/charts`), so treat its +> module paths as provisional rather than a long-term API. > **Legacy note.** Widgets currently under `packages/widgets-toolkit/src/widgets/*` (e.g. > `sales-by-coupon`, `sales-by-utm`) predate this layout and are scheduled to be migrated. > Do not use them as templates for new work — follow the structure and story template below -> instead. (This layout was first built for the **Average items per order** widget, -> [#49505](https://github.com/Automattic/jetpack/pull/49505).) +> instead. ### REQUIRED: widget folder structure @@ -296,14 +300,3 @@ controls, so there's no need to add custom size decorators per widget. legacy widgets that haven't been migrated yet. - Using the legacy `withWidgetRoot()` decorator for new stories — new widgets render via the real `WidgetDashboard` through the shared story helper instead. -- Putting the story at the wrong title path (e.g. `Packages/Premium Analytics/Widgets - Toolkit/Widgets/` — that's the legacy path). -- Omitting `registerReportMocks()` at the top of the story file when the widget fetches - report data — the widget will render an error state. -- Forgetting to register `projects/packages/premium-analytics/widgets` in - `projects/js-packages/storybook/storybook/projects.js` — the story won't appear in - Storybook at all. -- Omitting `tags: [ 'autodocs' ]`, which silently drops the widget from the autogenerated - docs page. -- Reimplementing data fetching or chart wiring inside `render.tsx` instead of composing - primitives from `@jetpack-premium-analytics/widgets-toolkit`.