What happened
The Storybook smoke on CI intermittently fails product-composer-slash-menu--context-switch-starts-with-a-loading-catalog (4 of the 36 PR runs that reached the smoke between 2026-09-05 19:39Z and 2026-09-06 03:03Z, e.g. run 34007377176) and its sibling product-shell-official-appshell--plus-menu-during-skill-refresh (run 34003729007), both with:
[product-composer-slash-menu--context-switch-starts-with-a-loading-catalog (light/default)] console.error: HL: expect(element).toBeVisible()
Received element is not visible:
<div aria-label="添加上下文" class="astryx-dropdown-menu … maka-composer-plus-panel" id="_r_3_" role="menu" tabindex="-1" />
The menu never closed. jest-dom's toBeVisible walks up to the Astryx layer element ([popover="auto"]) and reads its computed opacity. The layer enters through layerAnimations (@astryxdesign/core/dist/Layer/layerAnimations.stylex.js), a keyframe animation with animation-fill-mode: backwards, so until the first frame ticks the animation the computed opacity is the from value, 0. jest-dom treats exactly '0' as hidden. On a 2 vCPU runner driving four pages, showPopover() can go 200+ ms without a rendering frame, and the assertion lands inside that window.
#4889 retries the slash-menu assertion with waitFor, which is sufficient for that one site (the waitFor stops at the first ticked frame, or times out if the menu did close). Its description attributes the failure to the menu "being between renders"; the popup is stable, only its computed opacity is. The same read exists in the app-shell sibling (app-shell.stories.tsx, PlusMenuDuringSkillRefresh) and after the final findByRole('listbox') in both stories, and the app-shell harness holds the Skill refresh on a setTimeout(…, 150) that a slow runner outlives before the row is clicked.
How to reproduce
Never on macOS (44 runs, including 6× CPU throttling). Reproduces in the Playwright Linux image with the story rendered on four concurrent pages:
docker run --rm --cpus=2 -v "$PWD:$PWD:ro" -v "$PWD/scratch:/scratch" \
-e ROUNDS=40 -e CONC=4 mcr.microsoft.com/playwright:v1.62.1-noble node /scratch/repro-slash.mjs
where repro-slash.mjs opens iframe.html?id=<story>&viewMode=story on apps/desktop/storybook-static the way scripts/storybook-visual-smoke.mjs does, and hooks getComputedStyle, showPopover and animation events.
Trace of one failure (origin/main d2d7efe, before #4889):
1110ms showPopover div#_r_4_ inner=menu/添加上下文
1200ms click div role=menuitem label=选择技能 busy=true
1330ms gcsHit div#_r_4_ display=block opacity=0 visibility=visible popoverOpen=true :: window.getComputedStyle < Jl < Xl < Xl < Xl < Object.Zl (jest-dom toBeVisible)
1343ms animationstart x1ahk7ht-B div#_r_4_ inner=menu/添加上下文
1496ms animationend x1ahk7ht-B div#_r_4_ inner=menu/添加上下文
Same span in a passing run: showPopover 376ms, animationstart 403ms, click 414ms. The ancestor chain dumped a few hundred milliseconds after the failure is fully visible (display=block opacity=1 popover=auto open=true, no running animations).
The app-shell sibling fails for the timer as well: 40 container runs, 25 failures, of which 18 toBeVisible failures show the editor already containing / and the Skill listbox open (the click landed after the 150ms hold expired and activated the row for real), and 8 fail earlier at toHaveAttribute('aria-busy', 'true').
Environment
CI ubuntu-24.04, scripts/storybook-visual-smoke.mjs at concurrency 4, headless Chromium from Playwright 1.62.1. Stories introduced by #4825.
Logs, screenshots, or additional context
Emulating prefers-reduced-motion: reduce in the smoke script is not a fix: Astryx drops the layer animation under it, but scroll-motion-policy.ts also switches to instant scrolling and appshell--offscreen-active-turns-stay-findable / appshell--first-rail-click-lands-on-its-prompt-and-holds fail by design (the latter asserts "a reduced-motion browser finishes the jump in one frame and this story stops testing anything").
What happened
The Storybook smoke on CI intermittently fails
product-composer-slash-menu--context-switch-starts-with-a-loading-catalog(4 of the 36 PR runs that reached the smoke between 2026-09-05 19:39Z and 2026-09-06 03:03Z, e.g. run 34007377176) and its siblingproduct-shell-official-appshell--plus-menu-during-skill-refresh(run 34003729007), both with:The menu never closed. jest-dom's
toBeVisiblewalks up to the Astryx layer element ([popover="auto"]) and reads its computedopacity. The layer enters throughlayerAnimations(@astryxdesign/core/dist/Layer/layerAnimations.stylex.js), a keyframe animation withanimation-fill-mode: backwards, so until the first frame ticks the animation the computed opacity is thefromvalue,0. jest-dom treats exactly'0'as hidden. On a 2 vCPU runner driving four pages,showPopover()can go 200+ ms without a rendering frame, and the assertion lands inside that window.#4889 retries the slash-menu assertion with
waitFor, which is sufficient for that one site (thewaitForstops at the first ticked frame, or times out if the menu did close). Its description attributes the failure to the menu "being between renders"; the popup is stable, only its computed opacity is. The same read exists in the app-shell sibling (app-shell.stories.tsx,PlusMenuDuringSkillRefresh) and after the finalfindByRole('listbox')in both stories, and the app-shell harness holds the Skill refresh on asetTimeout(…, 150)that a slow runner outlives before the row is clicked.How to reproduce
Never on macOS (44 runs, including 6× CPU throttling). Reproduces in the Playwright Linux image with the story rendered on four concurrent pages:
where
repro-slash.mjsopensiframe.html?id=<story>&viewMode=storyonapps/desktop/storybook-staticthe wayscripts/storybook-visual-smoke.mjsdoes, and hooksgetComputedStyle,showPopoverand animation events.Trace of one failure (
origin/maind2d7efe, before #4889):Same span in a passing run:
showPopover376ms,animationstart403ms, click 414ms. The ancestor chain dumped a few hundred milliseconds after the failure is fully visible (display=block opacity=1 popover=auto open=true, no running animations).The app-shell sibling fails for the timer as well: 40 container runs, 25 failures, of which 18
toBeVisiblefailures show the editor already containing/and the Skill listbox open (the click landed after the 150ms hold expired and activated the row for real), and 8 fail earlier attoHaveAttribute('aria-busy', 'true').Environment
CI
ubuntu-24.04,scripts/storybook-visual-smoke.mjsat concurrency 4, headless Chromium from Playwright 1.62.1. Stories introduced by #4825.Logs, screenshots, or additional context
Emulating
prefers-reduced-motion: reducein the smoke script is not a fix: Astryx drops the layer animation under it, butscroll-motion-policy.tsalso switches to instant scrolling andappshell--offscreen-active-turns-stay-findable/appshell--first-rail-click-lands-on-its-prompt-and-holdsfail by design (the latter asserts "a reduced-motion browser finishes the jump in one frame and this story stops testing anything").