From b6f128ab09a9f03b474619caa77053f036a096b0 Mon Sep 17 00:00:00 2001 From: Ehab Younes Date: Tue, 18 Aug 2026 15:48:36 +0300 Subject: [PATCH] fix: skip Pixel-flaky menu/spinner/icon/progress-bar motion, fix VSCodeParity Menu capture CSS animations are a likely source of flaky Pixel diffs: a capture can land mid-animation depending on timing, and infinite animations (spinner rotation, icon spin, indeterminate progress-bar travel) are never fast-forwarded by Playwright's animations:"disabled", so they freeze at whatever frame happens to be current. isPixel() (from @coder/pixel-storybook) is the documented hook for this but was unused anywhere in the repo; preview.ts now sets a data-pixel attribute from it so each component's CSS can zero out its animation only during captures. Also: - ci.yaml now reports the real PR head commit to Pixel (PIXEL_COMMIT) instead of GitHub's synthetic pull_request merge SHA, which changes on every run and was never seen on a prior build of the same PR. - The VSCodeParity "Menu" story was only capturing our DropdownMenu, not the VscodeContextMenu reference beside it. Switching to defaultOpen with an invisible trigger anchor renders both from the first paint, matching the reference's always-open show prop, and drops the unneeded visible "Menu" button from the comparison. --- .github/workflows/ci.yaml | 2 ++ .storybook/preview.ts | 6 ++++++ packages/ui/src/components/Icon/Icon.css | 5 +++++ packages/ui/src/components/Menu/Menu.css | 8 ++++++++ .../ui/src/components/ProgressBar/ProgressBar.css | 5 +++++ packages/ui/src/components/Spinner/Spinner.css | 5 +++++ packages/ui/src/vscode-parity.stories.tsx | 12 ++++-------- 7 files changed, 35 insertions(+), 8 deletions(-) diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9abcf78e92..6068fecda0 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -110,6 +110,8 @@ jobs: run: pnpm exec pixel-storybook env: PIXEL_KEY: ${{ secrets.PIXEL_KEY }} + # On pull_request, github.sha is a synthetic merge commit, not the PR head. + PIXEL_COMMIT: ${{ github.event.pull_request.head.sha || github.sha }} # Auto-approve on mainline to avoid blocking CI after squash merges. PIXEL_AUTO_REVIEW: ${{ github.ref == 'refs/heads/main' }} diff --git a/.storybook/preview.ts b/.storybook/preview.ts index ad5833c39f..ef464fb77a 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -1,5 +1,6 @@ /// +import { isPixel } from "@coder/pixel-storybook/storyapi"; import codiconCssUrl from "@vscode/codicons/dist/codicon.css?url"; import { createElement } from "react"; @@ -29,6 +30,11 @@ if (typeof window !== "undefined") { }); } +// Lets us skip motion animation during Pixel captures. +if (typeof document !== "undefined" && isPixel()) { + document.documentElement.setAttribute("data-pixel", "true"); +} + // Inject codicon stylesheet immediately (before any components render) // Must be a element with id "vscode-codicon-stylesheet" for vscode-elements if ( diff --git a/packages/ui/src/components/Icon/Icon.css b/packages/ui/src/components/Icon/Icon.css index 42a3429401..9967622c11 100644 --- a/packages/ui/src/components/Icon/Icon.css +++ b/packages/ui/src/components/Icon/Icon.css @@ -13,3 +13,8 @@ animation: none; } } + +/* data-pixel is set in .storybook/preview.ts via isPixel(). */ +:root[data-pixel] .ui-icon--spin { + animation: none; +} diff --git a/packages/ui/src/components/Menu/Menu.css b/packages/ui/src/components/Menu/Menu.css index c4a10c0e0a..ec56a68507 100644 --- a/packages/ui/src/components/Menu/Menu.css +++ b/packages/ui/src/components/Menu/Menu.css @@ -140,6 +140,14 @@ } } +/* data-pixel is set in .storybook/preview.ts via isPixel(). */ +@media (prefers-reduced-motion: no-preference) { + :where(:root[data-pixel]) .ui-menu[data-state="open"], + :where(:root[data-pixel]) .ui-menu[data-state="closed"] { + animation: none; + } +} + @media (forced-colors: active) { .ui-menu__item[data-highlighted], .ui-menu__item[data-state="open"] { diff --git a/packages/ui/src/components/ProgressBar/ProgressBar.css b/packages/ui/src/components/ProgressBar/ProgressBar.css index dc31caec85..5dcdd4453b 100644 --- a/packages/ui/src/components/ProgressBar/ProgressBar.css +++ b/packages/ui/src/components/ProgressBar/ProgressBar.css @@ -52,3 +52,8 @@ background: Highlight; } } + +/* data-pixel is set in .storybook/preview.ts via isPixel(). */ +:root[data-pixel] .ui-progress-bar--indeterminate .ui-progress-bar__indicator { + animation: none; +} diff --git a/packages/ui/src/components/Spinner/Spinner.css b/packages/ui/src/components/Spinner/Spinner.css index 023c91a016..eb32eb4ba4 100644 --- a/packages/ui/src/components/Spinner/Spinner.css +++ b/packages/ui/src/components/Spinner/Spinner.css @@ -50,3 +50,8 @@ border-inline-end-color: Highlight; } } + +/* data-pixel is set in .storybook/preview.ts via isPixel(). */ +:root[data-pixel] .ui-spinner { + animation: none; +} diff --git a/packages/ui/src/vscode-parity.stories.tsx b/packages/ui/src/vscode-parity.stories.tsx index d37c2067f4..a9a7e49a93 100644 --- a/packages/ui/src/vscode-parity.stories.tsx +++ b/packages/ui/src/vscode-parity.stories.tsx @@ -24,7 +24,7 @@ import { ProgressBar } from "./components/ProgressBar/ProgressBar"; import { SearchInput } from "./components/SearchInput/SearchInput"; import { Spinner } from "./components/Spinner/Spinner"; import { StatusPill } from "./components/StatusPill/StatusPill"; -import { openMenu, PIXEL_ALL_THEMES } from "./storybook"; +import { PIXEL_ALL_THEMES } from "./storybook"; import type { Meta, StoryObj } from "@storybook/react-vite"; @@ -177,8 +177,7 @@ const Parity = (): React.JSX.Element => ( ); -/* The reference menu renders inline; ours is a real portalled DropdownMenu, - so the play function opens it under its trigger. */ +/* defaultOpen + an invisible trigger mirror the reference's `show`. */ const MenuParity = (): React.JSX.Element => (
( fontSize: "13px", }} > - + - + Start workspace @@ -228,7 +227,4 @@ export const SideBySide: Story = {}; export const Menu: Story = { render: () => , - play: async ({ canvasElement }) => { - await openMenu(canvasElement, "Menu"); - }, };