mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-05-22 13:32:11 +00:00
*PR Created by the Glary-Bot Agent* --- ## Summary Fixes the bug where the last badge in `ContentSection`'s sticky sidebar nav stays unhighlighted when the user scrolls to the very bottom of the page on tall viewports (reported on a 14" MacBook M4 Pro at 3024×1964 / 2016×1310 logical, both Chrome and Safari). ## Root cause The scroll-spy uses an IntersectionObserver with `rootMargin: '-20% 0px -60% 0px'`, which makes only a 20%–40% horizontal band from the viewport top "active". When multiple intersecting entries are reported, the callback picks the one whose `boundingClientRect.top` is smallest (highest up on screen). On tall viewports, when the page is scrolled to the absolute bottom, the last *and* the second-to-last sections frequently both sit inside that 20%–40% band at the same time. The "smallest top" tiebreak then selects the second-to-last section, leaving the last badge inactive even though the user has reached the end of the page. ## Fix `apps/website/src/components/common/ContentSection.vue`: 1. Add `isAtBottom()` — true when the viewport bottom has reached the document bottom (within 4px to absorb sub-pixel rounding). 2. The IntersectionObserver callback bails out when `isAtBottom()` so it cannot overwrite the choice below. 3. A passive `scroll` listener (and a one-shot `onMounted` call) sets `activeSection` to the last section whenever the page is at the bottom — including when the component mounts already at the bottom (e.g. hash navigation to a trailing anchor, restored scroll position, or a page shorter than the viewport). 4. Both the scroll handler and the IO callback honor the existing `isScrolling` flag, so click-driven smooth scroll-to-section behavior is unchanged. ## Verification Reproduced the bug at viewport 2016×1310 (14" M4 Pro "More Space" mode) on `/privacy-policy`: - Before fix: at absolute bottom, IntersectionObserver picks `australian-privacy` (second-to-last) — bug confirmed via DOM inspection that showed multiple sections intersecting the active band, with the second-to-last winning the "smallest top" tiebreak. - After fix: - Scrolled to bottom → last badge `CONTACT` is active. - Scrolled to top → first badge `INTRO` is active. - Scrolled mid-page → correct mid-section is active. - Click on a badge → smooth scrolls and that badge becomes active. - Initial render at bottom (loaded `/privacy-policy#contact`, browser scrolls to the bottom on mount) → `CONTACT` active immediately. `pnpm typecheck` and `pnpm typecheck:website` pass; `pnpm lint` reports 0 errors; existing website unit tests pass. Note: The website app currently has no Vue component test setup (`vitest.config.ts` is configured for `node` env, no DOM). Adding component tests for this scroll-spy interaction would require setting up `happy-dom` and `@testing-library/vue` for the website app, which is out of scope for this bug fix. Fixes FE-604 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-12057-FE-604-fix-website-activate-last-section-badge-when-scrolled-to-bottom-3596d73d365081faa243f4dd8e6ee54a) by [Unito](https://www.unito.io) --------- Co-authored-by: Glary-Bot <glary-bot@users.noreply.github.com>
62 lines
1.8 KiB
TypeScript
62 lines
1.8 KiB
TypeScript
import { expect } from '@playwright/test'
|
|
|
|
import { test } from './fixtures/blockExternalMedia'
|
|
|
|
const M4_PRO_14_INCH_VIEWPORT = { width: 2016, height: 1310 }
|
|
const LAST_SECTION_HASH = '#contact'
|
|
|
|
test.describe(
|
|
'ContentSection scroll-spy @smoke',
|
|
{
|
|
annotation: [
|
|
{
|
|
type: 'issue',
|
|
description:
|
|
'https://linear.app/comfyorg/issue/FE-604/bug-bottom-badge-not-activating-on-scroll-at-high-resolution-3024x1964'
|
|
},
|
|
{
|
|
type: 'environment',
|
|
description:
|
|
'14" MacBook M4 Pro logical viewport reported in FE-604; /privacy-policy reproduces because of its short trailing sections'
|
|
}
|
|
]
|
|
},
|
|
() => {
|
|
test.use({ viewport: M4_PRO_14_INCH_VIEWPORT })
|
|
|
|
test('activates the last badge when user scrolls to the bottom', async ({
|
|
page
|
|
}) => {
|
|
await page.goto('/privacy-policy')
|
|
|
|
const sidebarNav = page.getByRole('navigation', {
|
|
name: 'Category filter'
|
|
})
|
|
const badges = sidebarNav.getByRole('button')
|
|
const lastBadge = badges.last()
|
|
|
|
await expect(badges.first()).toHaveAttribute('aria-pressed', 'true')
|
|
await expect(lastBadge).toHaveAttribute('aria-pressed', 'false')
|
|
|
|
await page.evaluate(() =>
|
|
window.scrollTo(0, document.documentElement.scrollHeight)
|
|
)
|
|
|
|
await expect(lastBadge).toHaveAttribute('aria-pressed', 'true')
|
|
})
|
|
|
|
test('activates the last badge when page mounts already at the bottom via trailing hash', async ({
|
|
page
|
|
}) => {
|
|
await page.goto(`/privacy-policy${LAST_SECTION_HASH}`)
|
|
|
|
const sidebarNav = page.getByRole('navigation', {
|
|
name: 'Category filter'
|
|
})
|
|
const lastBadge = sidebarNav.getByRole('button').last()
|
|
|
|
await expect(lastBadge).toHaveAttribute('aria-pressed', 'true')
|
|
})
|
|
}
|
|
)
|