From ad90ff9934519c2e4035ef7ef547eb5fd3031e76 Mon Sep 17 00:00:00 2001 From: Karthikeyan Padaikathu Date: Wed, 21 Oct 2026 13:46:28 -0700 Subject: [PATCH] fix: keep the latest CLI major as the default docs version The CLI docs build marked a release as the default only when its newest version exactly matched the `latest` dist-tag. When a new patch is published before `latest` is moved (for example 12.2.1 while `latest` is still 12.2.0), no release was marked latest, so the version menu rendered an empty "Current" item and the unversioned redirects were dropped from the default version's pages. Selecting a version also returned focus to the menu button while it still showed the previous version and was still expanded, so screen readers announced the old version and never announced the new one. - Mark the release whose major matches the `latest` dist-tag as default - Set v12 back to the default/latest variant in nav.yml - Only render the "Current" version group and items when they exist - Keep the version menu open until the selected version has loaded, then close it and move focus to the menu button once Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- cli/lib/build.js | 2 +- cli/test/index.js | 20 ++++++++++ content/nav.yml | 4 +- src/components/variant-select.js | 68 +++++++++++++++++++++----------- 4 files changed, 69 insertions(+), 25 deletions(-) diff --git a/cli/lib/build.js b/cli/lib/build.js index 320765443ed..46b31a74afb 100644 --- a/cli/lib/build.js +++ b/cli/lib/build.js @@ -83,7 +83,7 @@ const main = async ({loglevel, releases: rawReleases, useCurrent, navPath, conte ...release, version: version.toString(), // the default release is always controlled by the latest dist-tag - default: semver.eq(version, pack.latest), + default: semver.major(version) === semver.major(pack.latest), prerelease: version.prerelease.length > 0, } ) diff --git a/cli/test/index.js b/cli/test/index.js index dbfae1d05c1..1c659786b80 100644 --- a/cli/test/index.js +++ b/cli/test/index.js @@ -219,6 +219,26 @@ test('earlier release is latest', async t => { await build() }) +test('major containing the latest dist-tag stays latest when a newer patch is not tagged yet', async t => { + const {build, testdir, releases} = await mockBuild(t, { + packument: {versions: ['6.14.18', '7.24.2', '8.19.3', '9.0.0', '9.0.1'], latest: '9.0.0'}, + }) + + await build() + + const nav = yaml.parse(await fs.readFile(join(testdir, 'nav.yml'), 'utf-8')) + const builtUrls = releases.map(r => `/cli/${r.id}`) + const variants = nav.find(c => c.url === '/cli').variants.filter(v => builtUrls.includes(v.url)) + const v9 = variants.find(v => v.url === '/cli/v9') + assert.equal(v9.title, 'Version 9.0.1') + assert.equal(v9.type, 'latest') + assert.equal(v9.default, true) + assert.deepEqual( + variants.filter(v => v.type === 'latest').map(v => v.url), + ['/cli/v9'], + ) +}) + test('can skip fetching latest', async t => { const {build} = await mockBuild(t) diff --git a/content/nav.yml b/content/nav.yml index a6789c6e25e..c0ccdd60183 100644 --- a/content/nav.yml +++ b/content/nav.yml @@ -1897,8 +1897,8 @@ - title: Version 12.2.1 shortName: v12 url: /cli/v12 - default: false - type: legacy + default: true + type: latest children: - title: CLI Commands shortName: Commands diff --git a/src/components/variant-select.js b/src/components/variant-select.js index 30f9a5db95b..6dfcfd1623e 100644 --- a/src/components/variant-select.js +++ b/src/components/variant-select.js @@ -31,49 +31,73 @@ const VariantItem = ({title, shortName, url, active}) => { ) } -const useVariantFocus = () => { - const locationChange = useLocationChange() - const anchorRef = React.useRef(null) - - React.useEffect(() => { - if (locationChange.change && getNav.didVariantChange(locationChange.previous, locationChange.current)) { - anchorRef.current.focus() - } - }, [locationChange]) - - return anchorRef -} +const isPlainActivation = event => + !event.metaKey && + !event.ctrlKey && + !event.shiftKey && + !event.altKey && + (event.type === 'click' ? event.button === 0 : event.key === 'Enter') const VariantMenu = ({title, latest, current, prerelease, legacy}) => { const [open, setOpen] = React.useState(false) - const anchorRef = useVariantFocus() + const anchorRef = React.useRef(null) + const selectingRef = React.useRef(false) + const focusAnchorRef = React.useRef(false) const labelId = 'label-versions-list-item' const locationChange = useLocationChange() React.useEffect(() => { + selectingRef.current = false if (locationChange.change && getNav.didVariantChange(locationChange.previous, locationChange.current)) { + focusAnchorRef.current = true setOpen(false) } // eslint-disable-next-line react-hooks/exhaustive-deps -- granular deps are intentional; locationChange.current is a URL string, not a ref }, [locationChange.change, locationChange.current, locationChange.previous]) + React.useEffect(() => { + if (!open && focusAnchorRef.current) { + focusAnchorRef.current = false + anchorRef.current?.focus() + } + }, [open, locationChange]) + + const handleOpenChange = React.useCallback(nextOpen => { + if (!nextOpen && selectingRef.current) { + return + } + setOpen(nextOpen) + }, []) + + const trackSelection = React.useCallback(event => { + const item = event.target.closest?.('[role="menuitem"]') + selectingRef.current = Boolean(item && !item.hasAttribute('aria-current') && isPlainActivation(event)) + }, []) + + const closeMenu = React.useCallback(() => { + selectingRef.current = false + setOpen(false) + }, []) + return ( <>

Select CLI Version:

- + {title} - setOpen(false)}> - - - Current - - {current && } - {prerelease && } - + + + {latest || current || prerelease ? ( + + Current + {latest && } + {current && } + {prerelease && } + + ) : null} Legacy {legacy.map(item => (