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
7 changes: 6 additions & 1 deletion .agents/skills/widget-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,12 @@ assume: namespace, text domain, and the dependency versions the package resolves
- Props are a named `type`/`interface` with each field documented on the type,
not echoed in `@param`. A component takes one typed tag
(`@param {Props} props - The component props.`) — never `@param props.<field>`
blocks; plain functions keep positional `@param`s.
blocks.
- The same one-typed-tag rule applies to ANY function taking an object argument
(hooks, story helpers, test builders): `@param {UseXArgs} args - Hook
arguments.` with fields documented on the args type — never `@param
args.<field>` / `@param root0.<field>` blocks. Only scalar positional
parameters keep plain per-param tags (`@param interval - The interval.`).
- Descriptions track the code: referenced symbols still exist, terminology is
consistent (`widget.json` ↔ `widget.ts` ↔ rendered strings), `@return` is accurate.
- Verify, don't guess: `jsdoc/require-param` is satisfied by that single typed
Expand Down
865 changes: 807 additions & 58 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: changed

Type widget attribute fields with WidgetAttributeField, export attribute types from widget.ts, and unify component JSDoc to single typed param tags across all widgets.
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: changed

Pin @wordpress/build to 0.19.1-next and the widget packages (@wordpress/widget-primitives, @wordpress/widget-dashboard) to 0.3.1-next development snapshots, scoped to this package.
6 changes: 3 additions & 3 deletions projects/packages/premium-analytics/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,8 @@
"@wordpress/theme": "0.17.0",
"@wordpress/ui": "0.17.0",
"@wordpress/url": "4.50.0",
"@wordpress/widget-dashboard": "0.2.0",
"@wordpress/widget-primitives": "0.2.0",
"@wordpress/widget-dashboard": "0.3.1-next.v.202607070741.0",
"@wordpress/widget-primitives": "0.3.1-next.v.202607070741.0",
"clsx": "2.1.1",
"date-fns": "4.1.0",
"fast-deep-equal": "^3.1.3",
Expand All @@ -91,7 +91,7 @@
"@types/react": "18.3.28",
"@typescript/native-preview": "7.0.0-dev.20260225.1",
"@wordpress/base-styles": "10.2.0",
"@wordpress/build": "0.18.0",
"@wordpress/build": "0.19.1-next.v.202607070741.0",
"browserslist": "4.28.4",
"jest": "30.4.2",
"storybook": "10.4.6",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const WidgetCard = ( {
height,
border: '1px solid var(--wpds-color-stroke-surface-neutral-weak, #e0e0e0)',
borderRadius: 'var(--wpds-border-radius-md, 8px)',
background: 'var(--wpds-color-background-surface-neutral-strong, #fff)',
background: 'var(--wpds-color-background-surface-neutral, #fff)',
display: 'flex',
flexDirection: 'column',
overflow: 'hidden',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function Dashboard(): JSX.Element {
DASHBOARD_NAME,
activeSection
);
const [ gridSettings, setGridSettings ] = useDashboardGridSettings();
const [ gridSettings ] = useDashboardGridSettings();

const widgetModules = useSelect(
select =>
Expand Down Expand Up @@ -74,7 +74,6 @@ function Dashboard(): JSX.Element {
onLayoutChange={ setLayout }
onLayoutReset={ resetLayout }
gridSettings={ gridSettings }
onGridSettingsChange={ setGridSettings }
editMode={ editMode }
onEditChange={ setEditMode }
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ function PostDetail(): JSX.Element {
const tabs = useMemo( () => getPostDetailTabs(), [] );
const [ activeTab, setActiveTab ] = useActiveTab();
const [ layout, setLayout, resetLayout ] = usePostDetailTabLayout( activeTab );
const [ gridSettings, setGridSettings ] = useDashboardGridSettings();
const [ gridSettings ] = useDashboardGridSettings();

const summary = usePostSummary( postId );

Expand Down Expand Up @@ -92,7 +92,6 @@ function PostDetail(): JSX.Element {
onLayoutChange={ setLayout }
onLayoutReset={ resetLayout }
gridSettings={ gridSettings }
onGridSettingsChange={ setGridSettings }
editMode={ editMode }
onEditChange={ setEditMode }
>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
"@wordpress/i18n": "^6.9.0",
"@wordpress/icons": "^15.0.0",
"@wordpress/ui": "0.17.0",
"@wordpress/widget-primitives": "next",
"date-fns": "4.1.0",
"@wordpress/widget-primitives": "0.2.0",
"react": "18.3.1"
},
"devDependencies": {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import type { WidgetRenderProps } from '@wordpress/widget-primitives';
// totals are all-time: the summary query takes no date params, so the picker's
// range and comparison state do not change what this widget shows.
type AllTimeStatsRenderAttributes = AllTimeStatsAttributes & Partial< ReportParamsFieldAttributes >;
type AllTimeStatsWidgetProps = WidgetRenderProps< AllTimeStatsRenderAttributes >;

/**
* The all-time summary carries dynamic WPCOM keys (`views`, `visitors`,
Expand Down Expand Up @@ -130,13 +131,10 @@ function AllTimeStatsReport() {
* range; report params still flow into WidgetRoot for parity with the other
* Stats widgets.
*
* @param props - Render props supplied by the widget host.
* @param props.attributes - Widget attributes.
* @param {AllTimeStatsWidgetProps} props - The widget render props.
* @return The rendered widget.
*/
export default function AllTimeStats( {
attributes = {},
}: WidgetRenderProps< AllTimeStatsRenderAttributes > ) {
export default function AllTimeStats( { attributes = {} }: AllTimeStatsWidgetProps ) {
return (
<WidgetRoot attributes={ attributes }>
<AllTimeStatsReport />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,17 @@ registerReportMocks();
const ALL_TIME_STATS_RENDER_MODULE = 'storybook/all-time-stats';

interface AllTimeStatsStoryControls {
/**
* Whether to include comparison report params.
*/
withComparison: boolean;
}

/**
* Renders the data-connected widget with report params derived from the
* date-range picker preset.
*
* @param props - The story controls.
* @param props.withComparison - Whether to include comparison report params.
* @param {AllTimeStatsStoryControls} props - The story controls.
* @return The rendered widget.
*/
function renderAllTimeStats( { withComparison }: AllTimeStatsStoryControls ) {
Expand Down Expand Up @@ -105,8 +107,7 @@ interface AllTimeStatsDashboardStoryProps
* Renders the data-connected widget through the shared dashboard harness, so it
* appears exactly as it does in product (framed card, sizing, edit mode).
*
* @param props - The dashboard story controls.
* @param props.withComparison - Whether to include comparison report params.
* @param {AllTimeStatsDashboardStoryProps} props - The dashboard story controls.
* @return The widget mounted inside the real `WidgetDashboard`.
*/
function AllTimeStatsDashboardStory( {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"@wordpress/i18n": "^6.9.0",
"@wordpress/icons": "^15.0.0",
"@wordpress/ui": "0.17.0",
"@wordpress/widget-primitives": "next",
"@wordpress/widget-primitives": "0.2.0",
"react": "18.3.1"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
type DataFormat,
type ReportParamsFieldAttributes,
} from '@jetpack-premium-analytics/widgets-toolkit';
import { __, sprintf } from '@wordpress/i18n';
import { __ } from '@wordpress/i18n';
import { arrowLeft, arrowRight, comment, paragraph, postList, starEmpty } from '@wordpress/icons';
import { Button, Icon, Text } from '@wordpress/ui';
import { useCallback, useMemo, useState } from 'react';
Expand All @@ -29,6 +29,12 @@ import type { WidgetRenderProps } from '@wordpress/widget-primitives';
// boundary (and Storybook may inject them) so the host contract holds.
type AnnualHighlightsRenderAttributes = AnnualHighlightsAttributes &
Partial< ReportParamsFieldAttributes >;
type AnnualHighlightsWidgetProps = WidgetRenderProps< AnnualHighlightsRenderAttributes >;

/**
* The enabled-metric flags from widget attributes, with defaults applied.
*/
type AnnualHighlightsReportProps = Required< AnnualHighlightsAttributes >;

const COUNT_FORMAT: DataFormat = {
type: 'number',
Expand All @@ -53,19 +59,15 @@ function sortYearsDescending( data?: StatsInsightsResponse ): StatsInsightsYear[
* comparison period, so each tile shows a bare formatted count. Which tiles
* appear is controlled by the per-metric visibility attributes.
*
* @param props - The enabled-metric flags from widget attributes.
* @param props.showPosts - Whether the Posts tile is shown.
* @param props.showWords - Whether the Words tile is shown.
* @param props.showLikes - Whether the Likes tile is shown.
* @param props.showComments - Whether the Comments tile is shown.
* @param {AnnualHighlightsReportProps} props - The component props.
* @return The widget content.
*/
function AnnualHighlightsReport( {
showPosts,
showWords,
showLikes,
showComments,
}: Required< AnnualHighlightsAttributes > ) {
}: AnnualHighlightsReportProps ) {
const { data, isLoading, isError } = useStatsInsights();

const years = useMemo( () => sortYearsDescending( data ), [ data ] );
Expand Down Expand Up @@ -151,40 +153,34 @@ function AnnualHighlightsReport( {

return (
<div className={ styles.root }>
<div className={ styles.header }>
<Text variant="heading-lg" render={ <h3 /> } className={ styles.title }>
{ sprintf(
/* translators: %s is a calendar year, e.g. "2026". */
__( '%s in review', 'jetpack-premium-analytics' ),
year.year
) }
</Text>
<div className={ styles.yearNav }>
<Button
type="button"
variant="minimal"
tone="neutral"
size="small"
className={ styles.navButton }
onClick={ showOlderYear }
disabled={ ! canShowOlder }
aria-label={ __( 'Previous year', 'jetpack-premium-analytics' ) }
>
<Button.Icon icon={ arrowLeft } size={ 16 } />
</Button>
<Button
type="button"
variant="minimal"
tone="neutral"
size="small"
className={ styles.navButton }
onClick={ showNewerYear }
disabled={ ! canShowNewer }
aria-label={ __( 'Next year', 'jetpack-premium-analytics' ) }
>
<Button.Icon icon={ arrowRight } size={ 16 } />
</Button>
</div>
<div className={ styles.yearNav }>
<Button
type="button"
variant="minimal"
tone="neutral"
size="small"
className={ styles.navButton }
onClick={ showOlderYear }
disabled={ ! canShowOlder }
aria-label={ __( 'Previous year', 'jetpack-premium-analytics' ) }
>
<Button.Icon icon={ arrowLeft } size={ 16 } />
</Button>

<Text className={ styles.yearLabel }>{ year.year }</Text>

<Button
type="button"
variant="minimal"
tone="neutral"
size="small"
className={ styles.navButton }
onClick={ showNewerYear }
disabled={ ! canShowNewer }
aria-label={ __( 'Next year', 'jetpack-premium-analytics' ) }
>
<Button.Icon icon={ arrowRight } size={ 16 } />
</Button>
</div>
{ tiles.length === 0 ? (
<Text className={ styles.placeholder }>
Expand Down Expand Up @@ -219,13 +215,10 @@ function AnnualHighlightsReport( {
* inner report. Host attributes are forwarded so any injected report params are
* preserved even though the insights endpoint is not period-scoped.
*
* @param props - Render props supplied by the widget host.
* @param props.attributes - Widget attributes.
* @param {AnnualHighlightsWidgetProps} props - The widget render props.
* @return The rendered widget.
*/
export default function AnnualHighlights( {
attributes = {},
}: WidgetRenderProps< AnnualHighlightsRenderAttributes > ) {
export default function AnnualHighlights( { attributes = {} }: AnnualHighlightsWidgetProps ) {
return (
<WidgetRoot attributes={ attributes }>
<AnnualHighlightsReport
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,24 +23,38 @@ registerReportMocks();
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, and
// surface the full-bleed presentation so it renders edge-to-edge with its own
// header instead of inside the host's framed chrome. The attribute schema is
// typed loosely on the widget definition, so it is cast to the WidgetType shape.
// 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: 'full-bleed' as const,
};

interface AnnualHighlightsStoryControls {
/**
* Whether to inject comparison report params.
*/
withComparison: boolean;
/**
* Whether the Posts tile is shown.
*/
showPosts: boolean;
/**
* Whether the Words tile is shown.
*/
showWords: boolean;
/**
* Whether the Likes tile is shown.
*/
showLikes: boolean;
/**
* Whether the Comments tile is shown.
*/
showComments: boolean;
}

Expand All @@ -52,12 +66,7 @@ interface AnnualHighlightsStoryControls {
* host injects comparison params. The metric toggles mirror the widget's
* checkbox settings and hide/show the matching tile.
*
* @param props - Story controls.
* @param props.withComparison - Whether to inject comparison report params.
* @param props.showPosts - Whether the Posts tile is shown.
* @param props.showWords - Whether the Words tile is shown.
* @param props.showLikes - Whether the Likes tile is shown.
* @param props.showComments - Whether the Comments tile is shown.
* @param {AnnualHighlightsStoryControls} props - Story controls.
* @return The rendered widget.
*/
function renderAnnualHighlights( {
Expand Down Expand Up @@ -150,12 +159,7 @@ interface AnnualHighlightsDashboardStoryProps
/**
* Renders the real registered widget through the shared dashboard harness.
*
* @param props - Dashboard and widget controls.
* @param props.withComparison - Whether to inject comparison report params.
* @param props.showPosts - Whether the Posts tile is shown.
* @param props.showWords - Whether the Words tile is shown.
* @param props.showLikes - Whether the Likes tile is shown.
* @param props.showComments - Whether the Comments tile is shown.
* @param {AnnualHighlightsDashboardStoryProps} props - Dashboard and widget controls.
* @return The rendered dashboard with the widget.
*/
function AnnualHighlightsDashboardStory( {
Expand Down
Loading
Loading