From d92495487fcb93f72eaa3f40250e51e276daa589 Mon Sep 17 00:00:00 2001 From: "paseo-bot[bot]" <266920839+paseo-bot[bot]@users.noreply.github.com> Date: Sat, 7 Nov 2026 21:28:12 +0000 Subject: [PATCH] fix(website): enable the language menu after hydration (#9046) The server-rendered trigger accepted clicks before its handler existed. Tie its enabled state to hydration and cover delayed scripts so an early click cannot silently disappear. Co-authored-by: paseo-bot[bot] <266920839+paseo-bot[bot]@users.noreply.github.com> --- packages/website/e2e/i18n.spec.ts | 30 +++++++++++++++++++ .../src/components/language-selector.tsx | 4 ++- 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/packages/website/e2e/i18n.spec.ts b/packages/website/e2e/i18n.spec.ts index 02d17e92cb..79fa1fa2bb 100644 --- a/packages/website/e2e/i18n.spec.ts +++ b/packages/website/e2e/i18n.spec.ts @@ -9,6 +9,23 @@ async function chooseLanguage(page: Page, language: string) { await page.getByRole("menuitemradio", { name: language }).click(); } +async function withPageScriptsHeld(page: Page, run: (release: () => void) => Promise) { + let releaseScripts!: () => void; + const scriptsReleased = new Promise((resolve) => { + releaseScripts = resolve; + }); + await page.route("**/*.js", async (route) => { + await scriptsReleased; + await route.continue(); + }); + try { + await run(releaseScripts); + } finally { + releaseScripts(); + await page.unrouteAll({ behavior: "wait" }); + } +} + function describeAlternates(links: Element[]): string[] { return links.map((link) => `${link.getAttribute("hreflang")} ${link.getAttribute("href")}`); } @@ -114,6 +131,7 @@ test("shows the language as an abbreviation and lists every language by name", a test("switches language from the keyboard", async ({ page }) => { await page.goto("/"); + await expect(languageButton(page)).toBeEnabled(); await languageButton(page).focus(); await page.keyboard.press("Enter"); await expect(page.getByRole("menuitemradio", { name: "English" })).toBeFocused(); @@ -183,3 +201,15 @@ test("shows the changelog in Chinese", async ({ page }) => { await expect(page.getByRole("heading", { name: "新增" }).first()).toBeVisible(); await expect(page.getByRole("note")).toHaveCount(0); }); + +test("waits for scripts before enabling the language menu", async ({ page }) => { + await withPageScriptsHeld(page, async (releaseScripts) => { + await page.goto("/docs/cli#running-agents", { waitUntil: "commit" }); + await expect(languageButton(page)).toBeVisible(); + await expect(languageButton(page)).toBeDisabled(); + + releaseScripts(); + await languageButton(page).click(); + await expect(page.getByRole("menuitemradio", { name: "English" })).toBeVisible(); + }); +}); diff --git a/packages/website/src/components/language-selector.tsx b/packages/website/src/components/language-selector.tsx index 9fb219fad5..601663f534 100644 --- a/packages/website/src/components/language-selector.tsx +++ b/packages/website/src/components/language-selector.tsx @@ -1,4 +1,4 @@ -import { useLocation, useNavigate } from "@tanstack/react-router"; +import { useHydrated, useLocation, useNavigate } from "@tanstack/react-router"; import { AnimatePresence, motion, type Transition } from "framer-motion"; import { Check, ChevronDown, Languages } from "lucide-react"; import { useCallback, useEffect, useId, useLayoutEffect, useRef, useState } from "react"; @@ -33,6 +33,7 @@ type OpenFocus = "current" | "first" | "last"; * the menu button pattern: arrows move between languages, Escape and outside presses close it. */ export function LanguageSelector() { + const hydrated = useHydrated(); const { t } = useTranslation(); const locale = useLocale(); const location = useLocation(); @@ -125,6 +126,7 @@ export function LanguageSelector() {