From f837936a2ed490e204aaa1085df6855279728618 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Tue, 4 Aug 2026 08:55:07 +1200 Subject: [PATCH 1/8] Charts: stop pre-bundling @wordpress/ui in the package build @wordpress/build 0.18.0 only externalizes @wordpress/* packages that declare wpScriptModuleExports. @wordpress/ui declares wpScript: false and none, so wp-build bundles it into charts' own output instead of treating it as an external import. The CHARTS-163 premise that window.wp.ui would be missing at runtime no longer holds, so there's no reason to keep pre-bundling it. Worse, pre-bundling is what forces Rolldown to emit a throwing dynamic require() shim for a transitive CommonJS dependency, which breaks as soon as anything reachable from charts source imports from @wordpress/ui. Removing '@wordpress/ui' from deps.alwaysBundle in tsdown.config.ts lets it be externalized as a normal ESM import: dist/index.js drops from ~433KB to ~351KB, dist/index.css is unchanged, and the dynamic require shim is gone. --- projects/js-packages/charts/tsdown.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/projects/js-packages/charts/tsdown.config.ts b/projects/js-packages/charts/tsdown.config.ts index 7d7cfeb14247..c3bc346c95e2 100644 --- a/projects/js-packages/charts/tsdown.config.ts +++ b/projects/js-packages/charts/tsdown.config.ts @@ -45,7 +45,7 @@ export default defineConfig( { '.png': 'asset', }, deps: { - alwaysBundle: [ '@wordpress/ui', /^fast-deep-equal/ ], + alwaysBundle: [ /^fast-deep-equal/ ], }, css: { fileName: 'index.css', From 3261318248d28752501c49aaf77c2d9e9cee6718 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Tue, 4 Aug 2026 09:00:41 +1200 Subject: [PATCH 2/8] Charts: restore IconButton for the chart zoom-reset control The zoom-reset button used a plain Button with aria-label + title as a stopgap, because IconButton's Base UI tooltip pulled in a CommonJS dependency that made Rolldown emit a dynamic require() into dist, which threw in WordPress Script Module consumers. title is invisible to keyboard users and can't be dismissed, failing WCAG 2.2 SC 1.4.13, and pairs badly with aria-label in some screen readers. A prior task removed @wordpress/ui from tsdown's deps.alwaysBundle, so it's now an external ESM import instead of being inlined, which removes the dynamic-require path entirely and makes IconButton safe to use again. Its tooltip appears on focus and dismisses on Escape. Tests were rewritten first to assert the new tooltip behaviour and observed failing against the old Button implementation before this swap. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HGjYD4EWfBUvwUZJ9ZF7yi --- .../private/x-zoom/test/x-zoom.test.tsx | 30 ++++++-- .../src/charts/private/x-zoom/x-zoom.tsx | 69 ++++++++----------- 2 files changed, 53 insertions(+), 46 deletions(-) diff --git a/projects/js-packages/charts/src/charts/private/x-zoom/test/x-zoom.test.tsx b/projects/js-packages/charts/src/charts/private/x-zoom/test/x-zoom.test.tsx index 8e32db8c7ada..749d6033271b 100644 --- a/projects/js-packages/charts/src/charts/private/x-zoom/test/x-zoom.test.tsx +++ b/projects/js-packages/charts/src/charts/private/x-zoom/test/x-zoom.test.tsx @@ -1,4 +1,4 @@ -import { act, render, renderHook, screen } from '@testing-library/react'; +import { act, render, renderHook, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { useRef } from 'react'; import { useXZoom, ZoomResetButton } from '../index'; @@ -147,17 +147,35 @@ const preventDefaultKeydown = ( event: ReactKeyboardEvent< HTMLDivElement > ) => event.preventDefault(); describe( 'ZoomResetButton', () => { - test( 'renders a labelled button with a hover tooltip', () => { + test( 'renders a labelled button without a native title tooltip', () => { const noop = jest.fn(); render( ); const button = screen.getByTestId( 'chart-zoom-reset' ); expect( button.tagName ).toBe( 'BUTTON' ); expect( button ).toHaveClass( 'x-zoom__reset' ); expect( button ).toHaveAccessibleName( 'Reset zoom' ); - // WPDS `IconButton` would supply a tooltip of its own, but it pulls in a - // CommonJS dependency that breaks Script Module consumers (see - // ZoomResetButton). `title` restores the hover hint on plain `Button`. - expect( button ).toHaveAttribute( 'title', 'Reset zoom' ); + // IconButton renders a real tooltip, so the `title` fallback is gone — + // `title` is invisible to keyboard users and cannot be dismissed. + expect( button ).not.toHaveAttribute( 'title' ); + } ); + + test( 'shows a tooltip on keyboard focus', async () => { + const noop = jest.fn(); + render( ); + await userEvent.tab(); + expect( screen.getByTestId( 'chart-zoom-reset' ) ).toHaveFocus(); + // The button's only text is the tooltip's — its own label is an + // `aria-label` attribute, so this cannot match the trigger. + await expect( screen.findByText( 'Reset zoom' ) ).resolves.toBeVisible(); + } ); + + test( 'dismisses the tooltip on Escape', async () => { + const noop = jest.fn(); + render( ); + await userEvent.tab(); + await expect( screen.findByText( 'Reset zoom' ) ).resolves.toBeVisible(); + await userEvent.keyboard( '{Escape}' ); + await waitFor( () => expect( screen.queryByText( 'Reset zoom' ) ).not.toBeInTheDocument() ); } ); test( 'fires onClick when activated', async () => { diff --git a/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.tsx b/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.tsx index dfe68335c62a..4b916051dda7 100644 --- a/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.tsx +++ b/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.tsx @@ -1,6 +1,6 @@ import { DataContext } from '@visx/xychart'; import { __ } from '@wordpress/i18n'; -import { Button } from '@wordpress/ui'; +import { IconButton } from '@wordpress/ui'; import { useCallback, useContext, useMemo, useState } from 'react'; import styles from './x-zoom.module.scss'; import type { SingleChartRef } from '../single-chart-context'; @@ -172,14 +172,8 @@ export function ZoomClip( { /** * Visible icon-only reset control rendered as an HTML overlay on top of the - * chart container, using the WPDS `Button`. The host should wrap its SVG in a - * `position: relative` container so the button anchors correctly. - * - * `IconButton` would be the natural fit, but it renders a Base UI tooltip whose - * CommonJS `use-sync-external-store` dependency makes Rolldown emit a dynamic - * `require()` into `dist`, which throws on evaluation in WordPress Script - * Module consumers. `Button` gives the same treatment without that dependency; - * the tooltip is replaced by `aria-label` + `title`. + * chart container, using the WPDS `IconButton`. The host should wrap its SVG in + * a `position: relative` container so the button anchors correctly. * * @param props - Props. * @param props.onClick - Click handler. Typically the `reset` from `useXZoom`. @@ -194,44 +188,39 @@ export function ZoomResetButton( { onClick }: { onClick: () => void } ) { event.stopPropagation(); } }, [] ); - const label = __( 'Reset zoom', 'jetpack-charts' ); return ( - + icon={ + + } + /> ); } From db539b809fd102a05850755af7a24935520008c9 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Tue, 4 Aug 2026 09:05:34 +1200 Subject: [PATCH 3/8] Charts: Remove redundant IconButton property overrides MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These three custom properties (--wp-ui-button-aspect-ratio, --wp-ui-button-padding-inline, --wp-ui-button-min-width) were a verbatim copy of the composition layer that IconButton itself applies. They were added as a workaround because IconButton could not be used in this component — its Base UI tooltip dependency included CommonJS code that Rolldown would emit as a dynamic require(), breaking Script Module consumers. Task 2 replaced Button with @wordpress/ui's IconButton, which resolved that blocker. These properties are now dead weight that would silently drift from upstream if not removed. Co-Authored-By: Claude Haiku 4.5 Claude-Session: https://claude.ai/code --- .../src/charts/private/x-zoom/x-zoom.module.scss | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.module.scss b/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.module.scss index 6ec9eafd5c1e..f8f606fedda6 100644 --- a/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.module.scss +++ b/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.module.scss @@ -11,8 +11,8 @@ pointer-events: none; } - // Overlay placement and icon-only geometry — the visual treatment (border, - // hover/focus, sizing) comes from the WPDS Button. The elevation shadow is + // Overlay placement only — border, hover/focus, sizing and icon-only + // geometry all come from the WPDS IconButton. The elevation shadow is // deliberately kept for separation from chart content (CHARTS-237 // design call, carrying over CHARTS-200's tokenization). `@wordpress/theme` // 1.0.0 dropped the `--wpds-elevation-*` group, so this role carries the @@ -24,16 +24,6 @@ z-index: 2; box-shadow: var(--a8c-charts-elevation-xs, 0 1px 1px 0 #00000008, 0 1px 2px 0 #00000005, 0 3px 3px 0 #00000005, 0 4px 4px 0 #00000003); - // `Button` is sized for a text label. These are the same three custom - // properties @wordpress/ui's own IconButton composition sets to make a - // square icon-only control; we set them directly because IconButton - // itself cannot be used here — it renders a Base UI tooltip, whose - // CommonJS `use-sync-external-store` dependency makes Rolldown emit a - // dynamic `require()` that throws in Script Module consumers. - --wp-ui-button-aspect-ratio: 1; - --wp-ui-button-padding-inline: 0; - --wp-ui-button-min-width: unset; - // WPDS outline-neutral is transparent-bodied at rest; give this // floating control an opaque body so chart content doesn't show // through it. Scoped to the rest state so the WPDS From 191a4e6a2900553edaaf413b12b042e1d23043d4 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Tue, 4 Aug 2026 09:19:02 +1200 Subject: [PATCH 4/8] Charts: document the dist vs jetpack:src consumption split The @wordpress/ui shim incident (CHARTS-247) happened partly because this split was not widely known: @wordpress/build apps (premium- analytics, publicize, podcast, videopress) consume the Rolldown dist/ output as a WordPress Script Module, where require does not exist, while webpack apps like My Jetpack resolve source through the jetpack:src export condition instead. A charts-only change did not look capable of breaking Jetpack Social because the two paths weren't documented anywhere. Record both the split and the no-alwaysBundle rule that caused the incident so the next dist-only change gets evaluated against the right blast radius. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HGjYD4EWfBUvwUZJ9ZF7yi --- projects/js-packages/charts/AGENTS.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/projects/js-packages/charts/AGENTS.md b/projects/js-packages/charts/AGENTS.md index 7c9ddbdd00a1..0487b2fe2ce8 100644 --- a/projects/js-packages/charts/AGENTS.md +++ b/projects/js-packages/charts/AGENTS.md @@ -33,6 +33,21 @@ The package is migrating to WordPress UI and Theme as its defaults. When adding `--wpds-*` mappings). Charts reference `--a8c-charts-*` roles with the mapped `var(--wpds-*, )` as the inline fallback; there is no runtime emission yet (that is CHARTS-203). +- **Two consumption paths — this changes what a charts change can break.** + `@wordpress/build` apps (premium-analytics, publicize, podcast, videopress) + consume the Rolldown output in `dist/` and load it as a **WordPress Script + Module** — native browser ESM, where `require` does not exist. Webpack apps + (My Jetpack and friends) resolve source through the `jetpack:src` export + condition instead. A change that only affects `dist/` can therefore break + four packages this one does not import. +- **Do not add packages to `deps.alwaysBundle` in `tsdown.config.ts`.** + Pre-bundling a dependency with `react` external forces Rolldown to emit a + dynamic-`require` shim for any transitive CommonJS module, and that shim + throws during module evaluation in Script Module consumers — taking down + every widget on the page, not just the feature that pulled it in. Leave + dependencies external and let each consumer's bundler resolve them. + `tools/assert-no-dynamic-require.ts` fails the build if a shim reaches the + ESM output; never suppress it. ## Documentation Workflow From 589e7ea4fbcf9f3b8c6ef710e3565d3fe57c13ed Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Tue, 4 Aug 2026 11:27:07 +1200 Subject: [PATCH 5/8] Charts: add changelog entry Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HGjYD4EWfBUvwUZJ9ZF7yi --- .../charts/changelog/charts-247-externalize-wordpress-ui | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 projects/js-packages/charts/changelog/charts-247-externalize-wordpress-ui diff --git a/projects/js-packages/charts/changelog/charts-247-externalize-wordpress-ui b/projects/js-packages/charts/changelog/charts-247-externalize-wordpress-ui new file mode 100644 index 000000000000..4ce75c276512 --- /dev/null +++ b/projects/js-packages/charts/changelog/charts-247-externalize-wordpress-ui @@ -0,0 +1,4 @@ +Significance: minor +Type: changed + +Zoom: Restore the accessible tooltip on the reset control, and stop bundling `@wordpress/ui` into the package output so each consumer's bundler resolves it. From 2dc08f9bb665671bae00947740b3b4d4c54b33c4 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Tue, 4 Aug 2026 12:23:32 +1200 Subject: [PATCH 6/8] Charts: record the runtime risk the build guard cannot catch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Externalising @wordpress/ui removes the dynamic-require shim, but it does not remove the failure mode — it moves it out of reach of the build. With the package external, dist stays clean by construction, so assert-no-dynamic-require can no longer detect this class of bug at all. Correctness now rests on @wordpress/build choosing to bundle @wordpress/ui rather than externalise it to window.wp.ui, which is what it used to do and what CHARTS-163 worked around. If that reverts, every build still passes and Script Module consumers break at runtime instead. A PR description is not read at the next @wordpress/build bump, so record it in AGENTS.md with the versions actually verified. Also narrow the alwaysBundle rule to what is really true. It read as a blanket prohibition while the config one line away still bundles fast-deep-equal, which invites a reader to discount the whole rule. The danger is specific to pre-bundling a package that transitively requires an external; a dependency-free package cannot produce a shim. Document the tooltip in both zoom docs sections, which disagreed with each other and described neither, and cover the one path that could strand a portaled tooltip: activation unmounts the button while its tooltip is open. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HGjYD4EWfBUvwUZJ9ZF7yi --- projects/js-packages/charts/AGENTS.md | 31 ++++++++++++++----- .../charts/area-chart/stories/index.docs.mdx | 2 +- .../charts/line-chart/stories/index.docs.mdx | 2 +- .../private/x-zoom/test/x-zoom.test.tsx | 29 ++++++++++++++++- 4 files changed, 53 insertions(+), 11 deletions(-) diff --git a/projects/js-packages/charts/AGENTS.md b/projects/js-packages/charts/AGENTS.md index 0487b2fe2ce8..4c27538cfbb5 100644 --- a/projects/js-packages/charts/AGENTS.md +++ b/projects/js-packages/charts/AGENTS.md @@ -40,14 +40,29 @@ The package is migrating to WordPress UI and Theme as its defaults. When adding (My Jetpack and friends) resolve source through the `jetpack:src` export condition instead. A change that only affects `dist/` can therefore break four packages this one does not import. -- **Do not add packages to `deps.alwaysBundle` in `tsdown.config.ts`.** - Pre-bundling a dependency with `react` external forces Rolldown to emit a - dynamic-`require` shim for any transitive CommonJS module, and that shim - throws during module evaluation in Script Module consumers — taking down - every widget on the page, not just the feature that pulled it in. Leave - dependencies external and let each consumer's bundler resolve them. - `tools/assert-no-dynamic-require.ts` fails the build if a shim reaches the - ESM output; never suppress it. +- **Never `deps.alwaysBundle` a package that transitively requires an external.** + Pre-bundling is safe only for dependencies that require nothing themselves — + `fast-deep-equal` qualifies, which is why `tsdown.config.ts` still lists it. + Pre-bundle anything that reaches a CommonJS module requiring an external + (`react`, above all) and Rolldown emits a dynamic-`require` shim, because it + cannot rewrite a runtime `require` into a static ESM import. That shim throws + during module evaluation in Script Module consumers, taking down every widget + on the page rather than just the feature that pulled it in. + `tools/assert-no-dynamic-require.ts` fails the build when such a shim reaches + the ESM output; never suppress it. +- **`@wordpress/ui` is external in `dist`, and no build check can prove that is + safe.** Correctness depends on `@wordpress/build` *bundling* `@wordpress/ui` + rather than externalising it to `window.wp.ui`. It used to externalise it, + which is what CHARTS-163 worked around by pre-bundling; it now bundles any + `@wordpress/*` package that declares neither `wpScriptModuleExports` nor + `wpScript`, and `@wordpress/ui` declares `wpScript: false`. If a future + version reverts, `dist/index.js` keeps its clean `import … from + "@wordpress/ui"`, the guard passes, the build passes, and every Script Module + consumer breaks at runtime on `wp.ui` being undefined — the same blast radius + as CHARTS-237, with no build-time signal. Verified against `@wordpress/build` + 0.18.0 (publicize, podcast, videopress) and 0.19.1-next (premium-analytics). + Check a major bump by loading a charts screen in wp-admin, not by trusting a + green build. ## Documentation Workflow diff --git a/projects/js-packages/charts/src/charts/area-chart/stories/index.docs.mdx b/projects/js-packages/charts/src/charts/area-chart/stories/index.docs.mdx index 604cd28627df..6e744be7c1aa 100644 --- a/projects/js-packages/charts/src/charts/area-chart/stories/index.docs.mdx +++ b/projects/js-packages/charts/src/charts/area-chart/stories/index.docs.mdx @@ -95,7 +95,7 @@ Zoom behaviour: - Only the X axis rescales; the Y axis is unaffected. - A selection rectangle follows the pointer while dragging. - Drags shorter than 6px are ignored, so a click never zooms. -- While zoomed, a "Reset zoom" button appears in the top-right to restore the full domain. It is reachable with Tab and activates with Enter or Space. +- While zoomed, a "Reset zoom" button appears in the top-right to restore the full domain. It is reachable with Tab and activates with Enter or Space. A "Reset zoom" tooltip shows on hover and on keyboard focus, and Escape dismisses it. - Areas are clipped to the plot area whenever `zoomable` is set, keeping the zoom-out animation within the axes. - `zoomable` chains with your own `onPointerDown`/`onPointerMove`/`onPointerUp` handlers rather than replacing them. diff --git a/projects/js-packages/charts/src/charts/line-chart/stories/index.docs.mdx b/projects/js-packages/charts/src/charts/line-chart/stories/index.docs.mdx index 0bafe97cab0c..fd639b38c393 100644 --- a/projects/js-packages/charts/src/charts/line-chart/stories/index.docs.mdx +++ b/projects/js-packages/charts/src/charts/line-chart/stories/index.docs.mdx @@ -527,7 +527,7 @@ Zoom behaviour: - Only the X axis rescales; the Y axis is unaffected. - A selection rectangle follows the pointer while dragging. - Drags shorter than 6px are ignored, so a click never zooms. -- While zoomed, a "Reset zoom" button appears in the top-right to restore the full domain. +- While zoomed, a "Reset zoom" button appears in the top-right to restore the full domain. It is reachable with Tab and activates with Enter or Space. A "Reset zoom" tooltip shows on hover and on keyboard focus, and Escape dismisses it. - Series are clipped to the plot area while zoomed, so lines never overflow the axes. - `zoomable` chains with your own `onPointerDown`/`onPointerMove`/`onPointerUp` handlers rather than replacing them. diff --git a/projects/js-packages/charts/src/charts/private/x-zoom/test/x-zoom.test.tsx b/projects/js-packages/charts/src/charts/private/x-zoom/test/x-zoom.test.tsx index 749d6033271b..8b19e04b5fcd 100644 --- a/projects/js-packages/charts/src/charts/private/x-zoom/test/x-zoom.test.tsx +++ b/projects/js-packages/charts/src/charts/private/x-zoom/test/x-zoom.test.tsx @@ -1,6 +1,6 @@ import { act, render, renderHook, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; -import { useRef } from 'react'; +import { useCallback, useRef, useState } from 'react'; import { useXZoom, ZoomResetButton } from '../index'; import type { SingleChartRef } from '../../single-chart-context'; import type { EventHandlerParams } from '@visx/xychart'; @@ -185,6 +185,33 @@ describe( 'ZoomResetButton', () => { expect( onClick ).toHaveBeenCalledTimes( 1 ); } ); + test( 'leaves no orphaned tooltip when activation unmounts the button', async () => { + // Resetting the zoom unmounts this control while its tooltip is open. + // The tooltip renders in a portal outside the container, so a missed + // cleanup would strand it on the page rather than remove it with the + // button. + /** + * Mirrors the host charts: the reset control exists only while zoomed. + * + * @return JSX element or null. + */ + function Host() { + const [ zoomed, setZoomed ] = useState( true ); + const unzoom = useCallback( () => setZoomed( false ), [] ); + return zoomed ? : null; + } + render( ); + await userEvent.tab(); + await expect( screen.findByText( 'Reset zoom' ) ).resolves.toBeVisible(); + + await userEvent.keyboard( '{Enter}' ); + + await waitFor( () => + expect( screen.queryByTestId( 'chart-zoom-reset' ) ).not.toBeInTheDocument() + ); + expect( document.body ).not.toHaveTextContent( 'Reset zoom' ); + } ); + test( 'keyboard activation survives the chart wrapper keydown handler', async () => { const onClick = jest.fn(); // Mirrors the chart's grid wrapper, whose keyboard-navigation handler From 47b6cd2875103041eae109b4e0228338e8fa8828 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Wed, 5 Aug 2026 15:35:04 +1200 Subject: [PATCH 7/8] Improve comment --- .../src/charts/private/x-zoom/x-zoom.module.scss | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.module.scss b/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.module.scss index f8f606fedda6..18285a1581fb 100644 --- a/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.module.scss +++ b/projects/js-packages/charts/src/charts/private/x-zoom/x-zoom.module.scss @@ -11,12 +11,12 @@ pointer-events: none; } - // Overlay placement only — border, hover/focus, sizing and icon-only - // geometry all come from the WPDS IconButton. The elevation shadow is - // deliberately kept for separation from chart content (CHARTS-237 - // design call, carrying over CHARTS-200's tokenization). `@wordpress/theme` - // 1.0.0 dropped the `--wpds-elevation-*` group, so this role carries the - // former token's spec value directly rather than nesting a `--wpds-*` var. + // The elevation shadow is deliberately kept for separation + // from chart content (CHARTS-237 design call, carrying + // over CHARTS-200's tokenization). `@wordpress/theme` 1.0.0 + // dropped the `--wpds-elevation-*` group, so this role carries + // the former token's spec value directly rather than nesting + // a `--wpds-*` var. &__reset { position: absolute; top: var(--wpds-dimension-gap-sm, 8px); From 4592a6af091b1f35bf6ad97ff73fd68600f55f87 Mon Sep 17 00:00:00 2001 From: Adam Wood <1017872+adamwoodnz@users.noreply.github.com> Date: Wed, 5 Aug 2026 16:33:29 +1200 Subject: [PATCH 8/8] Charts: mark the @wordpress/ui externalization as a breaking change The package is past 1.0, so minor promises consumers on ^1.x that this is safe to take automatically. It is not universally safe: dist now imports @wordpress/ui at runtime, and any bundler that externalizes @wordpress/* to window.wp.* resolves that to an undefined window.wp.ui. That is not a hypothetical. CHARTS-163 added the pre-bundling this PR removes precisely because externalized @wordpress/ui broke WooCommerce Analytics that way, and the failure is a blank screen at runtime rather than a build error. Also state the requirement in the entry itself, so the release notes tell a consumer what to check rather than only what changed. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01HGjYD4EWfBUvwUZJ9ZF7yi --- .../charts/changelog/charts-247-externalize-wordpress-ui | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/projects/js-packages/charts/changelog/charts-247-externalize-wordpress-ui b/projects/js-packages/charts/changelog/charts-247-externalize-wordpress-ui index 4ce75c276512..b60d43bbd739 100644 --- a/projects/js-packages/charts/changelog/charts-247-externalize-wordpress-ui +++ b/projects/js-packages/charts/changelog/charts-247-externalize-wordpress-ui @@ -1,4 +1,4 @@ -Significance: minor +Significance: major Type: changed -Zoom: Restore the accessible tooltip on the reset control, and stop bundling `@wordpress/ui` into the package output so each consumer's bundler resolves it. +Zoom: Restore the accessible tooltip on the reset control. `@wordpress/ui` is no longer bundled into the package output, so each consumer's bundler now resolves it. It remains a dependency and resolves from node_modules by default, but a bundler that externalizes `@wordpress/*` to `window.wp.*` must bundle `@wordpress/ui` instead — `window.wp.ui` does not exist.