From be4678aea6225bd346caa118be10356f97d90781 Mon Sep 17 00:00:00 2001 From: dognose24 Date: Fri, 26 Jun 2026 15:14:56 +0800 Subject: [PATCH 1/4] Premium Analytics: document widget conventions and Stats porting patterns MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Consolidates conventions surfaced during code review of the Locations (#49513), Devices (#49928), and Search Terms (#49985) ports into shared agent guidance so future batch ports have a single source of truth. - `.agents/rules/widgets.md` — adds Two-component structure section (outer WidgetRoot / inner useWidgetRootContext, reportParams from context) - `AGENTS.md` — adds package-wide pitfalls (alias naming, English comments) and new Widget pitfalls (presentation source of truth, attribute type locality, button type, inline styles, toolkit helpers); new Stats widgets section covering data hooks, data access path, max=0 semantics, loading strategy, comparison placeholders, and visual conventions - `.agents/skills/widget-audit.md` — extends checklist with presentation consistency, ghost attributes, alias names, WidgetRenderProps import, inline styles, button type, i18n scope, and English comments Co-Authored-By: Claude Sonnet 4.6 (1M context) --- .agents/rules/widgets.md | 25 +++++++ .agents/skills/widget-audit.md | 22 ++++-- projects/packages/premium-analytics/AGENTS.md | 71 +++++++++++++++++++ 3 files changed, 113 insertions(+), 5 deletions(-) diff --git a/.agents/rules/widgets.md b/.agents/rules/widgets.md index 7719b28070f5..7319703b659b 100644 --- a/.agents/rules/widgets.md +++ b/.agents/rules/widgets.md @@ -19,4 +19,29 @@ The host owns all chrome. The widget renders body only — never a `Card`, heade title, or remove control. `presentation` in `widget.json` declares how the widget wants to be framed; the host decides how. +## Two-component structure + +Every widget that reads dashboard state splits into two components: + +```tsx +// outer — receives host props, seeds WidgetRoot +export default function MyWidget( { attributes = {} }: { attributes?: MyAttributes } ) { + return ( + + + + ); +} + +// inner — reads dashboard context, does all data work +function MyWidgetInner() { + const { reportParams } = useWidgetRootContext(); + // ... +} +``` + +`useWidgetRootContext()` must be called inside a `` — calling it in the +outer component throws. `reportParams` always comes from context; the dashboard date +picker owns it. Never read date range from `attributes`. + diff --git a/.agents/skills/widget-audit.md b/.agents/skills/widget-audit.md index c2d989de79af..c11c163be030 100644 --- a/.agents/skills/widget-audit.md +++ b/.agents/skills/widget-audit.md @@ -31,13 +31,18 @@ assume: namespace, text domain, and the dependency versions the package resolves `category`, `presentation` (`framed` | `content-bleed` | `full-bleed`). - `widget.ts` default-exports `title`, `icon`, and — when configurable — `attributes` + `example`. The attribute TS shape is declared once and reused. + `widget.ts` does NOT declare `presentation` — that field belongs only in `widget.json`. +- Every attribute declared in `widget.ts` is consumed somewhere in `render.tsx`; flag any + ghost attribute (declared but never read from `attributes`). - `render.tsx` default-exports the component. - `package.json` `dependencies` mirror the imports across the widget source: - nothing missing, nothing unused. + nothing missing, nothing unused. Internal packages use `@jetpack-premium-analytics/*` + aliases (never `@automattic/jetpack-premium-analytics-*` names). **Contract** -- `render.tsx` props are typed by `WidgetRenderProps` from - `@wordpress/widget-primitives`, not a hand-rolled shape. +- `render.tsx` props are typed by `WidgetRenderProps` from + `@wordpress/widget-primitives`, not a hand-rolled shape. `T` is the attribute type + imported from `../widget` — it is NOT re-declared in `render.tsx`. - `attributes` is defended — the host may pass it empty or undefined, so default it. - The component receives only `{ attributes, setAttributes }`; no dashboard / surface / wp-admin imports, no `onRemove` / header / kebab. @@ -45,12 +50,19 @@ assume: namespace, text domain, and the dependency versions the package resolves **Chrome** - The body renders content only — never a `Card`, header, title bar, or remove control (the host owns chrome). `presentation` in `widget.json` is a valid value. +- Verify that the `presentation` value in `widget.json` matches the widget's actual layout + assumptions (e.g. `full-bleed` vs `framed` changes whether the host renders a border and + padding). A mismatch causes incorrect chrome without a compile error. **Style + i18n** - Every `--wpds-*` token in the CSS exists in the resolved `@wordpress/theme` `design-tokens.css` (grep it; do not infer renamed names). Styles are CSS - Modules, never global CSS. -- User-visible strings go through `__( …, '' )`. + Modules, never global CSS. No inline `style={{ … }}` props. +- Every `