Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .agents/rules/widgets.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ A registered dashboard widget is a folder under `widgets/`, auto-discovered by c
(no registration):

- `package.json` — workspace package for the lazy-loaded render bundle.
- `widget.json` — static metadata (name, title, description, category, presentation).
- `widget.ts` — live metadata (default export: title, icon, attributes, example).
- `widget.json` — static metadata (name, title, description, help, category, presentation).
- `widget.ts` — live, non-serializable metadata (default export: icon, attributes, example).
- `render.tsx` — default-export React component.
- `style.module.css` — optional; CSS Modules, tokens from `@wordpress/theme` (`--wpds-*`).

Expand Down
18 changes: 11 additions & 7 deletions projects/packages/premium-analytics/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,16 @@ Each new widget MUST ship as a self-contained folder with these files:
```text
widgets/<widget-name>/
├── 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)
├── widget.json # declarative metadata (name, title, description, help, category, presentation)
├── widget.ts # runtime-only definition (icon, attributes, example)
├── render.tsx # the React component, wrapped in <WidgetRoot> from widgets-toolkit
└── stories/<widget-name>-widget.stories.tsx
```

Notes:

- `name` in both `widget.json` and `widget.ts` MUST use the `jpa/` prefix
(e.g. `jpa/<widget-name>`).
- `name` lives in `widget.json` and MUST use the `jpa/` prefix
(e.g. `jpa/<widget-name>`). `widget.ts` no longer declares it.
- Keep `render.tsx` thin: compose toolkit primitives (`WidgetRoot`,
`OrderMetricWidget`, etc.) rather than reimplementing data fetching, chart wiring, or
theming.
Expand Down Expand Up @@ -318,10 +318,12 @@ import {
widgetDashboardWithWidgetArgTypes,
type WidgetDashboardWithWidgetControls,
} from '../../stories/widget-dashboard-with-widget';
import { createStoryWidgetType } from '../../stories/create-story-widget-type';
import { withWidgetCanvas } from '../../stories/with-widget-canvas';
import { registerReportMocks } from '../../../packages/widgets-toolkit/src/stories/mocks/register-report-mocks';
import MyWidgetRender from '../render';
import widgetDefinition from '../widget';
import widgetManifest from '../widget.json';
import type { Meta, StoryObj } from '@storybook/react';
import type { WidgetRenderProps } from '@wordpress/widget-primitives';
import type { ComponentProps, ComponentType } from 'react';
Expand Down Expand Up @@ -377,7 +379,7 @@ function MyWidgetDashboardStory( dashboardArgs: WidgetDashboardWithWidgetControl
return (
<WidgetDashboardWithWidgetStory
{ ...dashboardArgs }
widgetType={ widgetDefinition }
widgetType={ createStoryWidgetType( widgetManifest, widgetDefinition ) }
renderModule={ MY_WIDGET_RENDER_MODULE }
renderComponent={ MyWidgetRender as ComponentType< WidgetRenderProps< unknown > > }
attributes={ { reportParams: getDefaultQueryParams( true ) } }
Expand Down Expand Up @@ -476,8 +478,10 @@ stories so it hits the mock fresh instead of reading their cached success. See
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.
- Declaring `presentation` in `widget.ts` — `widget.json` is the source of truth for that
field; omit it from `widget.ts` entirely.
- Declaring `name`, `title`, `help`, `description`, `category`, or `presentation` in
`widget.ts` — `widget.json` is the source of truth for all declarative metadata; the
`widget.ts` default export carries only `icon`, `attributes`, and `example`. Stories read
those declarative fields from `widget.json` via `createStoryWidgetType()`.
- Re-declaring the attribute type in `render.tsx` — the shape is declared once in `widget.ts`
and imported in `render.tsx`; render-only types may compose that imported shape with host
fields like `Partial<ReportParamsFieldAttributes>`, but must not duplicate the shape.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Significance: patch
Type: changed
Comment: Widgets: author declarative metadata (name, title, help) in widget.json instead of widget.ts, and build story widget types from the manifest via a shared createStoryWidgetType() helper. The widget.ts default export now carries only icon, attributes, and example. No user-facing change.
Original file line number Diff line number Diff line change
Expand Up @@ -24,32 +24,27 @@ import {
widgetDashboardWithWidgetArgTypes,
type WidgetDashboardWithWidgetControls,
} from '../../stories/widget-dashboard-with-widget';
import { createStoryWidgetType } from '../../stories/create-story-widget-type';
import { withWidgetCanvas } from '../../stories/with-widget-canvas';
import AllTimeStatsRender from '../render';
import widgetDefinition, {
DEFAULT_ALL_TIME_STATS_METRICS,
type AllTimeStatsMetricId,
} from '../widget';
import widgetManifest from '../widget.json';
import type { Meta, StoryObj } from '@storybook/react';
import type { WidgetRenderProps, WidgetType } from '@wordpress/widget-primitives';
import type { WidgetRenderProps } from '@wordpress/widget-primitives';
import type { ComponentProps, ComponentType } from 'react';

registerReportMocks();

const ALL_TIME_STATS_RENDER_MODULE = 'storybook/all-time-stats';

// Carry the widget's metadata, including the metric-visibility attribute schema
// so the dashboard story's settings drawer renders the real checkboxes. The
// attribute schema is typed loosely on the widget definition, so it is cast to
// the WidgetType shape.
const storyWidgetType = {
name: widgetDefinition.name,
title: widgetDefinition.title,
icon: widgetDefinition.icon,
presentation: 'framed' as const,
attributes: widgetDefinition.attributes as WidgetType[ 'attributes' ],
example: widgetDefinition.example,
};
// so the dashboard story's settings drawer renders the real checkboxes.
// `presentation` comes from widget.json ( 'framed' ), so the host frames the
// widget and renders its identity (title + icon).
const storyWidgetType = createStoryWidgetType( widgetManifest, widgetDefinition );

interface AllTimeStatsStoryControls {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "jpa/all-time-stats",
"title": "All-time stats",
"description": "Lifetime totals for your site — views, visitors, posts, and comments.",
"help": {
"content": "Lifetime totals for your site — views, visitors, posts, and comments."
},
"category": "stats",
"presentation": "framed"
}
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,6 @@ export const DEFAULT_ALL_TIME_STATS_METRICS: AllTimeStatsMetricId[] = ALL_TIME_S
* doubles as the defaults applied to new instances: every metric enabled.
*/
export default {
name: 'jpa/all-time-stats',
title: __( 'All-time stats', 'jetpack-premium-analytics' ),
help: {
content: __(
'Lifetime totals for your site — views, visitors, posts, and comments.',
'jetpack-premium-analytics'
),
},
icon: trendingUp,
attributes: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,17 @@ import {
type WidgetDashboardWithWidgetControls,
} from '../../stories/widget-dashboard-with-widget';
import { withStoryRouter } from '../../stories/with-story-router';
import { createStoryWidgetType } from '../../stories/create-story-widget-type';
import { withWidgetCanvas } from '../../stories/with-widget-canvas';
import {
registerReportMocks,
setReportMockState,
} from '../../../packages/widgets-toolkit/src/stories/mocks/register-report-mocks';
import AnnualHighlightsRender from '../render';
import widgetDefinition, { DEFAULT_HIGHLIGHT_METRICS, type AnnualHighlightMetric } from '../widget';
import widgetManifest from '../widget.json';
import type { Meta, StoryObj } from '@storybook/react';
import type { WidgetRenderProps, WidgetType } from '@wordpress/widget-primitives';
import type { WidgetRenderProps } from '@wordpress/widget-primitives';
import type { ComponentProps, ComponentType } from 'react';

registerReportMocks();
Expand All @@ -33,16 +35,9 @@ const ANNUAL_HIGHLIGHTS_RENDER_MODULE = 'storybook/annual-highlights';

// Carry the widget's metadata, including the metric-visibility attribute schema
// so the dashboard story's settings drawer renders the real checkboxes.
// Presentation is left unset so the host frames the widget and renders its
// identity (title + icon), matching widget.json. The attribute schema is typed
// loosely on the widget definition, so it is cast to the WidgetType shape.
const storyWidgetType = {
name: widgetDefinition.name,
title: widgetDefinition.title,
icon: widgetDefinition.icon,
attributes: widgetDefinition.attributes as WidgetType[ 'attributes' ],
example: widgetDefinition.example,
};
// `presentation` comes from widget.json ( 'framed' ), so the host frames the
// widget and renders its identity (title + icon).
const storyWidgetType = createStoryWidgetType( widgetManifest, widgetDefinition );

interface AnnualHighlightsStoryControls {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "jpa/annual-highlights",
"title": "Annual highlights",
"description": "Your totals for the year at a glance — posts, words, likes, and comments.",
"help": {
"content": "Your totals for the year at a glance — posts, words, likes, and comments."
},
"category": "stats",
"presentation": "framed"
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,14 +40,6 @@ export const DEFAULT_HIGHLIGHT_METRICS: AnnualHighlightMetric[] = [
* metric enabled.
*/
export default {
name: 'jpa/annual-highlights',
title: __( 'Annual highlights', 'jetpack-premium-analytics' ),
help: {
content: __(
'Your totals for the year at a glance — posts, words, likes, and comments.',
'jetpack-premium-analytics'
),
},
icon: calendar,
attributes: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import {
widgetDashboardWithWidgetArgTypes,
type WidgetDashboardWithWidgetControls,
} from '../../stories/widget-dashboard-with-widget';
import { createStoryWidgetType } from '../../stories/create-story-widget-type';
import { withWidgetCanvas } from '../../stories/with-widget-canvas';
import {
registerReportMocks,
setReportMockState,
} from '../../../packages/widgets-toolkit/src/stories/mocks/register-report-mocks';
import AuthorsRender from '../render';
import widgetDefinition from '../widget';
import widgetManifest from '../widget.json';
import type { Meta, StoryObj } from '@storybook/react';
import type { WidgetRenderProps } from '@wordpress/widget-primitives';
import type { ComponentProps, ComponentType } from 'react';
Expand All @@ -20,12 +22,7 @@ registerReportMocks();

const AUTHORS_RENDER_MODULE = 'storybook/authors';

const storyWidgetType = {
name: widgetDefinition.name,
title: widgetDefinition.title,
icon: widgetDefinition.icon,
presentation: 'framed' as const,
};
const storyWidgetType = createStoryWidgetType( widgetManifest, widgetDefinition );

interface AuthorsStoryControls {
withComparison: boolean;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
"name": "jpa/authors",
"title": "Authors",
"description": "Top authors by views, with their most viewed posts.",
"help": {
"content": "The authors whose content received the most views.",
"links": [
{
"label": "Learn more",
"href": "https://jetpack.com/support/jetpack-stats/"
}
]
},
"category": "stats",
"presentation": "framed"
}
14 changes: 0 additions & 14 deletions projects/packages/premium-analytics/widgets/authors/widget.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,20 +21,6 @@ export type AuthorsAttributes = {
* Widget type definition.
*/
export default {
name: 'jpa/authors',
title: __( 'Authors', 'jetpack-premium-analytics' ),
help: {
content: __(
'The authors whose content received the most views.',
'jetpack-premium-analytics'
),
links: [
{
label: __( 'Learn more', 'jetpack-premium-analytics' ),
href: 'https://jetpack.com/support/jetpack-stats/',
},
],
},
icon: postAuthor,
attributes: [
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import {
widgetDashboardWithWidgetArgTypes,
type WidgetDashboardWithWidgetControls,
} from '../../stories/widget-dashboard-with-widget';
import { createStoryWidgetType } from '../../stories/create-story-widget-type';
import { withWidgetCanvas } from '../../stories/with-widget-canvas';
import {
registerReportMocks,
setReportMockState,
} from '../../../packages/widgets-toolkit/src/stories/mocks/register-report-mocks';
import AverageItemsPerOrderRender from '../render';
import widgetDefinition from '../widget';
import widgetManifest from '../widget.json';
import type { Meta, StoryObj } from '@storybook/react';
import type { WidgetRenderProps } from '@wordpress/widget-primitives';
import type { ComponentProps, ComponentType } from 'react';
Expand Down Expand Up @@ -100,7 +102,7 @@ function AverageItemsPerOrderDashboardStory( {
return (
<WidgetDashboardWithWidgetStory
{ ...dashboardStoryArgs }
widgetType={ widgetDefinition }
widgetType={ createStoryWidgetType( widgetManifest, widgetDefinition ) }
renderModule={ AVERAGE_ITEMS_RENDER_MODULE }
renderComponent={
AverageItemsPerOrderRender as ComponentType< WidgetRenderProps< unknown > >
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "jpa/average-items-per-order",
"title": "Average items per order",
"description": "Show the average number of products per order over a set period of time.",
"help": {
"content": "Show the average number of products per order over a set period of time."
},
"category": "orders",
"presentation": "framed"
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { chartBar } from '@wordpress/icons';

/**
Expand All @@ -23,13 +22,5 @@ export type AverageItemsPerOrderAttributes = Record< never, never >;
* dashboards can opt in.
*/
export default {
name: 'jpa/average-items-per-order',
title: __( 'Average items per order', 'jetpack-premium-analytics' ),
help: {
content: __(
'Show the average number of products per order over a set period of time.',
'jetpack-premium-analytics'
),
},
icon: chartBar,
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import {
widgetDashboardWithWidgetArgTypes,
type WidgetDashboardWithWidgetControls,
} from '../../stories/widget-dashboard-with-widget';
import { createStoryWidgetType } from '../../stories/create-story-widget-type';
import { withWidgetCanvas } from '../../stories/with-widget-canvas';
import {
registerReportMocks,
setReportMockState,
} from '../../../packages/widgets-toolkit/src/stories/mocks/register-report-mocks';
import AverageOrderValueRender from '../render';
import widgetDefinition from '../widget';
import widgetManifest from '../widget.json';
import type { Meta, StoryObj } from '@storybook/react';
import type { WidgetRenderProps } from '@wordpress/widget-primitives';
import type { ComponentProps, ComponentType } from 'react';
Expand Down Expand Up @@ -94,7 +96,7 @@ function AverageOrderValueDashboardStory( {
return (
<WidgetDashboardWithWidgetStory
{ ...dashboardStoryArgs }
widgetType={ widgetDefinition }
widgetType={ createStoryWidgetType( widgetManifest, widgetDefinition ) }
renderModule={ AVERAGE_ORDER_VALUE_RENDER_MODULE }
renderComponent={ AverageOrderValueRender as ComponentType< WidgetRenderProps< unknown > > }
attributes={ getAverageOrderValueAttributes( withComparison, preset ) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
"name": "jpa/average-order-value",
"title": "Average order value",
"description": "Track the average value of each order over a set period of time.",
"help": {
"content": "Track the average value of each order over a set period of time."
},
"category": "orders",
"presentation": "framed"
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
/**
* WordPress dependencies
*/
import { __ } from '@wordpress/i18n';
import { chartBar } from '@wordpress/icons';

/**
Expand All @@ -23,13 +22,5 @@ export type AverageOrderValueAttributes = Record< never, never >;
* dashboards can opt in.
*/
export default {
name: 'jpa/average-order-value',
title: __( 'Average order value', 'jetpack-premium-analytics' ),
help: {
content: __(
'Track the average value of each order over a set period of time.',
'jetpack-premium-analytics'
),
},
icon: chartBar,
};
Loading