fix(web): clip overflow instead of creating scroll containers

Closed
WI
5d ago
+148-136

Site CSS clips overflow with overflow: clip instead of overflow: hidden, so clipping boxes are no longer scroll containers. A scroll container captures position: sticky descendants (the cause of the /pear and /reflex sticky-nav bug) and can be scrolled by focus, find-in-page, or scrollIntoView, shifting clipped content.

What the CSS is now

  • 136 overflow: hidden declarations across 21 CSS modules are overflow: clip.
  • 19 declarations stay hidden, each listed below with its reason.
  • Boxes that relied on a scroll container's zero automatic minimum size now say so explicitly. clip keeps the content-based min-width: auto / min-height: auto of flex items, grid items and aspect-ratio boxes:
    • min-width: 0: landing .featurePreview, .searchResults strong, .searchResults span; reflex .assetFrame, .filePath; pear .visual; agents .rowTriggerText; docs .productHeaderTagline; IntegrationGrid .name
    • min-height: 0: landing .editorCode (column flex item); brand .kitPreview (aspect-ratio box)
  • docs .articleBody is display: flow-root + overflow: clip. It keeps the block formatting context that stops its first and last children's margins from collapsing through it.
  • The site has no floats, so no other element depends on the BFC that hidden created.

Layout evidence

Production builds (next build + next start) of the base branch and this branch were fingerprinted with headless Chromium at 1440×900 and 375×812. Each run scrolled through the page to trigger lazy content, paused CSS animations at t=0, and recorded the rounded document-space getBoundingClientRect of every rendered element plus documentElement.scrollWidth/scrollHeight. A second base run filtered out nondeterministic elements.

Pages: /, /docs/introduction, /blog, /blog/introducing-agent-relay, /pear, /reflex, /agents, /agents/cloudflare-monitor, /agents/register, /enterprise, /flows, /file, /primitives (where /schedule, /message, /auth redirect), /brand, /about, /process, /teams, /login, /messaging, /careers, /u/agent-relay, /docs/file/integrations, and a 404 page.

ViewportPagesElements comparedGeometry diffsDocument size diffs
1440×9002618,7713*0
375×8122618,6142*0

*All five are paths inside one hero SVG <g> on / and /messaging. That group is animated by script and moves 1–4px between runs; it also differed between the two base runs.

Elements compared per page (desktop / mobile)
PageDesktopMobile
/31023101
/docs/introduction666662
/blog237237
/blog/introducing-agent-relay456457
/pear452451
/reflex260260
/agents370368
/agents/cloudflare-monitor169168
/agents/register171170
/enterprise323322
/flows12951230
/file356351
/primitives (+ /schedule, /message, /auth)253 each252 each
/brand301300
/about17571756
/process161160
/teams31423077
/login8181
/messaging30583062
/careers8181
/u/agent-relay119118
/docs/file/integrations602598
404600596

The sticky site nav sits at top: 12px (desktop) and 8px (mobile) at mid-page on every page that has it, the same as on base.

npm --workspace web run build and npm --workspace web run test (555 passed, 1 skipped) pass.

Declarations kept as overflow: hidden (19)

Scrolled by script (2)

  • web/app/landing.module.css .chatStream overflow-x / overflow-y: ChannelMessagesPreview scrolls it with scrollTo / scrollTop to animate the message feed. It must stay a scroll container.

Unreachable modules (16). Nothing renders them, so a change can't be checked:

  • web/app/schedule/relaycron.module.css (3): /schedule redirects to /primitives#schedule, and ScheduleContent is not imported anywhere.
  • web/app/message/relaycast.module.css (6): /message redirects to /primitives#message, and RelaycastContent is not imported anywhere.
  • web/app/auth/relayauth.module.css (5): /auth redirects to /primitives#auth, and RelayauthContent is not imported anywhere.
  • web/app/flows/onboarding/onboarding.module.css .codePanel, .visuallyHidden (2): /flows/onboarding has a layout and no page, so it returns 404.

Modal dialog (1)

  • web/app/flows/onboarding/onboarding.module.css .agentEditor[open]: a <dialog> on the same unreachable route.

web/components/docs/docs-search.module.css is outside this change.

🤖 Generated with Claude Code


[!NOTE]
Low Risk
CSS-only overflow and min-size tweaks with layout fingerprinting showing no document-size regressions; low risk aside from verifying edge-case clipping on affected pages.

Overview
Replaces overflow: hidden with overflow: clip across marketing, docs, and shared component CSS so clipping no longer creates scroll containers. That fixes sticky navigation on pages like /pear and /reflex (ancestors with hidden used to trap position: sticky) and avoids unwanted scrolling from focus, find-in-page, or scrollIntoView inside clipped regions.

Where clip changes minimum sizing behavior, the PR adds explicit min-width: 0 or min-height: 0 on flex/grid children (e.g. ellipsis text, feature previews, kit previews). Docs .articleBody gets display: flow-root plus overflow: clip to preserve margin containment without relying on hidden's BFC.

Intentionally unchanged overflow: hidden remains for script-driven scroll areas (e.g. chat stream) and unreachable or modal-only styles noted in the PR description.

Reviewed by Cursor Bugbot for commit a142048f44af6cf6dc8a709229a3537bb25f0b7d. Bugbot is set up for automated code reviews on this repo. Configure here.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.


Summary by cubic

Changes the site's CSS to clip overflow with overflow: clip instead of overflow: hidden, so clipping boxes are no longer scroll containers. A scroll container captures position: sticky descendants (the cause of the /pear and /reflex sticky-nav bug) and can be scrolled by focus, find-in-page, or scrollIntoView, shifting clipped content.

Changes

  • 136 overflow: hidden declarations across 21 CSS modules become overflow: clip; 19 stay hidden because they're scrolled by script, a modal dialog, or in unreachable modules.
  • Boxes that relied on a scroll container's zero automatic minimum size now set min-width: 0 or min-height: 0.
  • docs .articleBody uses display: flow-root + overflow: clip to keep the block formatting context that stops child margins from collapsing.

Verification

  • Headless Chromium renders of 26 pages at 1440×900 and 375×812 show no layout diffs besides nondeterministic animated SVG paths.
  • The sticky nav sits at the same position on every page as on the base branch.
  • npm --workspace web run test passes (555 passed, 1 skipped).