From d5a6db289dec94e845fc1323503af06665a2acdd Mon Sep 17 00:00:00 2001 From: Tudor Date: Wed, 26 Aug 2026 21:07:48 +0100 Subject: [PATCH] fix(suggest): let the dropdown out of the hero panel MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit .heroPanel had overflow: hidden to clip its artwork and scrim to the rounded corners. It clipped the suggestion dropdown too. Measured on staging with the flag on: the list runs 482 to 802, the panel ends at 624 — so 178px of 320 was cut off, about half the options, with nothing on screen to say anything was missing. The two things that actually needed clipping now round themselves: .heroArt gets border-radius: inherit plus its own overflow, and the ::before scrim inherits the radius. Below 860px the artwork is a band flush with the top of the panel rather than a layer covering it, so it takes the top two corners only — inheriting all four would leave it floating with rounded corners against the copy. Nothing else depended on the panel clipping: .valueProps below it is entirely static, so a positioned dropdown paints above it without a z-index fight. The regression test asserts the LAST option is the element actually painted at its own coordinates. toBeVisible() would not have caught this — it checks for a non-empty box and visibility, and an ancestor's overflow clips neither. elementFromPoint catches clipping and occlusion alike. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_015mWQnpye9F299NVRCCSRvj --- e2e/tests/journeys.spec.ts | 35 +++++++++++++++++++++++ nextjs-app/components/HomeView.module.css | 24 +++++++++++++++- 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/e2e/tests/journeys.spec.ts b/e2e/tests/journeys.spec.ts index db3fab7..7d34f7b 100644 --- a/e2e/tests/journeys.spec.ts +++ b/e2e/tests/journeys.spec.ts @@ -2163,6 +2163,41 @@ test('typing a school name suggests it, and choosing it opens that school', asyn await expect(page).toHaveURL(/\/school\/\d+/); }); +test('the whole dropdown is reachable, not clipped by the hero', async ({ page }) => { + /* + * The hero panel had overflow: hidden to clip its artwork to the rounded + * corners, and it clipped the dropdown too — 320px of list against 145px of + * panel below the input, so roughly half was cut off with nothing to say so. + * + * toBeVisible() does not catch this: it checks the box is non-empty and not + * visibility:hidden, and an ancestor's overflow clips neither. The invariant + * that does catch it is that the LAST option is the thing actually painted + * at its own coordinates — which fails for clipping and for occlusion alike. + */ + test.skip(!(await autosuggestIsOn(page)), + 'the school_autosuggest flag is off in this environment'); + + const { schools } = await (await page.request.get('/api/schools?page_size=1')).json(); + test.skip(!schools?.length, 'no schools in this environment'); + + await page.goto('/'); + await page.getByRole('combobox').first().fill( + (schools[0].school_name as string).slice(0, 6)); + + const options = page.getByRole('option'); + await expect(options.first()).toBeVisible(); + const count = await options.count(); + + const painted = await options.nth(count - 1).evaluate((el) => { + const r = el.getBoundingClientRect(); + const hit = document.elementFromPoint(r.left + r.width / 2, r.top + r.height / 2); + return { inside: el.contains(hit) || el === hit, bottom: Math.round(r.bottom) }; + }); + expect(painted.inside, + `the last option is not painted at its own coordinates (bottom ${painted.bottom}) ` + + '— an ancestor is clipping or covering the dropdown').toBeTruthy(); +}); + test('with autosuggest off, the search box is a plain input', async ({ page }) => { test.skip(await autosuggestIsOn(page), 'the school_autosuggest flag is on in this environment'); diff --git a/nextjs-app/components/HomeView.module.css b/nextjs-app/components/HomeView.module.css index 252c1b0..206c290 100644 --- a/nextjs-app/components/HomeView.module.css +++ b/nextjs-app/components/HomeView.module.css @@ -90,7 +90,18 @@ isolation: isolate; background: var(--hero-ground); border-radius: var(--radius-xl); - overflow: hidden; + /* + * Deliberately NOT overflow: hidden. + * + * It used to be, to clip the artwork and the scrim to the rounded corners — + * and it also clipped the search box's suggestion dropdown, which is 320px + * tall against 145px of panel below the input. Roughly half the list was cut + * off with no indication anything was missing. + * + * The two things that actually needed clipping round themselves instead, so + * the panel can let a dropdown out. Anything absolutely positioned inside + * this panel and taller than the space below it depends on this. + */ } .heroContent { @@ -107,6 +118,10 @@ position: absolute; inset: 0; z-index: 0; + /* Rounds itself, because the panel no longer clips it. inset: 0 makes this + exactly the panel's own corners. */ + border-radius: inherit; + overflow: hidden; } .heroArt picture, @@ -143,6 +158,9 @@ inset: 0; z-index: 1; pointer-events: none; + /* Same reason as .heroArt: the panel stopped clipping, so the scrim keeps + its own corners rather than squaring off over the panel's. */ + border-radius: inherit; background: linear-gradient( to right, var(--hero-ground) 0%, @@ -331,6 +349,10 @@ position: static; order: -1; height: 13rem; + /* Top corners only. Here the artwork is a band flush with the top of the + panel, not a layer covering it — inheriting all four would leave it + floating with rounded bottom corners against the copy below. */ + border-radius: var(--radius-xl) var(--radius-xl) 0 0; } /* The band crop puts the schoolhouse at 73% across — reported by scripts/build-hero-images.js, which derives it from the crop box rather